模板和重载之间的交互

问题描述

以下代码输出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,因为模板 {{ 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-> 访问模板类成员以强制它们也依赖等。