Вывод следующего кода — TA
, но я не понимаю, почему.
#include <iostream>
#include <type_traits>
struct A {};
template<typename T>
void fun(T) {
std::cout << "T";
}
template<typename T>
void caller(T t) {
fun(A{});
fun(t);
}
void fun(A) {
std::cout << "A";
}
int main() {
caller(A{});
}
Мое понимание шаблонов говорит мне следующее:
main
единственная функция «на месте» — это нешаблонная перегрузка fun
, та, что печатает A
, потому что шаблоны fun
и caller
не использовались и, в свою очередь, не создавались экземпляры;caller
анализируется, шаблон caller
создается с помощью T = A
, что подразумевает, что caller
вызывает fun
с объектами одного типа в обоих случаях;A
, я бы сказал, что в обоих случаях вызывается нешаблонная перегрузка fun
.Однако вышесказанное неверно, иначе я бы получил AA
(на самом деле, тоже TT
меня меньше удивил бы, чем TA
).
Я также заметил, что при перемещении нешаблонной перегрузки перед определением caller
вывод становится AA
, поэтому я могу только предположить:
fun(A{});
, шаблон fun
создается с помощью T = A
, даже если шаблон caller
еще не создается;caller(A{});
анализируется, создается экземпляр caller
;fun
в теле caller
может быть интерпретирован как вызов с аргументом типа A
, но на этот раз нешаблон fun
уже известен компилятору, поэтому он выбран как лучшее соответствие.Я не знаю, имеет ли смысл вышеизложенное.
Более предсказуемо, если я использую следующую специализацию шаблона
template<>
void fun<A>(A) {
std::cout << "A";
}
вместо перегрузки без шаблона вывод всегда будет AA
, независимо от того, поместил ли я специализацию до или после определения caller
.
Я подозреваю, что замена нешаблонной перегрузки специализацией шаблона при сохранении этой специализации ниже вызова fun(A{})
является либо неопределенным поведением, либо неправильно сформированным отчетом о недоставке. Если я правильно помню, специализации должны быть объявлены до первого вызова, который использовал бы их, если бы они были видны.
@IgorTandetnik, думаю, вы правы: раздел Подробно здесь тому подтверждение.
fun(A{});
не включает никаких выражений, зависящих от параметров шаблона, поэтому поиск и разрешение перегрузки происходят в точке определения. В этот момент виден только fun(T)
; fun(A)
не участвует в разрешении перегрузок.
fun(t)
вызов зависит от T
, поэтому разрешение откладывается до момента инстанцирования. В этот момент видны как fun(A)
, так и fun(T)
, и fun(A)
побеждает: не-шаблоны предпочтительнее шаблонов, при прочих равных условиях.
Просто чтобы добавить к ответу Игоря Тандетника: если вы откладываете создание экземпляра, предоставляя поддельные параметры, разрешение перегрузки задерживается:
#include <iostream>
#include <type_traits>
struct A {};
template<typename T>
void fun(T) {
std::cout << "T";
}
template<typename T, typename AA = A> // <<==== HERE
void caller(T t) {
fun(AA{});
fun(t);
}
void fun(A) {
std::cout << "A";
}
int main() {
caller(A{}); // Does AA
}
Это обычная уловка для задержки разрешения выражений, не зависящих от параметров шаблона, например, добавление поддельных параметров в шаблоны для предотвращения заполнения специализаций, например:
template<typename T>
struct foo {};
template<>
struct foo<void> {}; // Instantiated immediately
template<typename T, int fake = 0>
struct bar {};
template<int fake>
struct bar<void, fake> {}; // Not instantiated until used
// Users of bar<T> almost never realize they use bar<T, 0>, you can even provide a wrapper without `int` parameter.
Иногда вам нужно получить доступ к члену класса шаблона с помощью this->
, чтобы заставить их также быть зависимыми и т. д.
Думайте об этом не с точки зрения времени, а с точки зрения пространства: ном-зависимые вызовы выполняют поиск с того места, где они появляются (только), поэтому учитываются только более ранние объявления.