问题描述
#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
,因为模板 {{ 1}} 和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
的参数的调用,但这次是非模板 { {1}} 已为编译器所知,因此被选为更好的匹配项。
我不知道以上是否有任何意义。
更可预见的是,如果我使用以下模板特化
fun
代替非模板重载,那么输出总是template<>
void fun<A>(A) {
std::cout << "A";
}
,不管我把特化放在AA
之前还是之后的定义。
解决方法
fun(A{});
不涉及任何模板参数相关表达式,因此查找和重载解析发生在定义点。此时,只有 fun(T)
可见; fun(A)
不参与重载决议。
fun(t)
调用依赖于 T
,因此解析延迟到实例化点。此时,fun(A)
和 fun(T)
都是可见的,fun(A)
获胜:非模板优先于模板,其他条件相同。
补充一下 Igor Tandetnik 的回答:如果您通过提供虚假参数来延迟实例化,则重载解析会延迟:
#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->
访问模板类成员以强制它们也依赖等。