模板和重载之间的交互
Interaction between templates and overloads
下面代码的输出是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
;
- 仅在此时,在
caller
的主体中对 fun
的第二次调用可以解释为具有类型 A
参数的调用,但这次是非模板 fun
已为编译器所知,因此选择它作为更好的匹配。
不过,我不知道以上是否有意义。
更可预测的是,如果我使用以下模板专业化
template<>
void fun<A>(A) {
std::cout << "A";
}
而不是非模板重载,那么输出总是AA
,无论我是否放专业化在 caller
的定义之前或之后。
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->
访问模板 class 成员以强制他们也依赖,等等。
下面代码的输出是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
; - 仅在此时,在
caller
的主体中对fun
的第二次调用可以解释为具有类型A
参数的调用,但这次是非模板fun
已为编译器所知,因此选择它作为更好的匹配。
不过,我不知道以上是否有意义。
更可预测的是,如果我使用以下模板专业化
template<>
void fun<A>(A) {
std::cout << "A";
}
而不是非模板重载,那么输出总是AA
,无论我是否放专业化在 caller
的定义之前或之后。
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->
访问模板 class 成员以强制他们也依赖,等等。