从模板实例化后声明的模板函数选择候选者调用C++模板重载决策

Mat*_*Tso 9 c++ templates overload-resolution

以下是模板上下文中一个非常奇怪的重载决策的极简主义示例:

#include <iostream>

// Types //
struct I { int v; };

template <class T>
struct D { T t; };

// Functions //

// Overload 1
template <class T>
I f(T) { return {1}; }

// Template indirection that calls f(T)
template <class T>
I g(D<T>) { return f(T{}); }

// Non template indirection that calls f(T)
I h(D<I>) { return f(I{}); }

int main() {
    std::cout
        << f(I{}).v     // f(I{}) overload called directly
        << "\n"         //    => overload 1 called
        << h(D<I>{}).v  // f(I{}) called though non-template
        << "\n"         //    => overload 1 called
        << g(D<I>{}).v  // f(I{}) called though template
        << "\n";        //    => overload 2 actually called ???
}

// Overload 2
// Should not be reachable as declared after all f(...) calls.
// If was a candidate, would be chosen over 1.
I f(I) { return {2}; }
Run Code Online (Sandbox Code Playgroud)

这似乎与ADL有关,因为如果I将其置于命名空间中,则始终会调用"重载1".

我知道ADL的执行就像调用模板实例化点(main)一样.

对于模板定义中使用的依赖名称,查询将被推迟,直到模板参数已知,此时ADL检查具有外部链接的函数声明(直到C++ 11),这些声明可从模板定义上下文以及模板实例化上下文,而非ADL查找仅检查从模板定义上下文可见的外部链接(直到C++ 11)的函数声明. http://en.cppreference.com/w/cpp/language/unqualified_lookup#Template_definition

但是这里"Overload 2"被宣布为AFTER main!main作为gand 的instanciation点f,我假设之前 宣布的函数main是超载候选者.

请注意,此行为与g模板相关,因为h(g等效而非模板函数)调用"Overload 1".

如何"重载2" main- 之后宣布- 被称为?

使用clang ++(3.8.1)和g ++(6.2.1)重现了这种行为.

T.C*_*.C. 5

[temp.dep.candidate]/1:

如果调用结果不正确或者找到更好的匹配,那么相关命名空间中的查找会考虑在所有翻译单元中的那些名称空间中引入外部链接的所有函数声明,而不仅仅是考虑模板定义和模板中的那些声明实例化上下文,然后程序有未定义的行为.

而且,[temp.point]/6,强调我的:

函数模板的特化可以在翻译单元中具有多个实例化点,并且除了上述实例化点之外,对于在翻译单元内具有实例化点的任何这样的专业化,结束翻译单位也被认为是实例化的一个方面.[...]如果两个不同的实例化点根据单一定义规则([basic.def.odr])给出模板特化的不同含义,则程序格式错误,无需诊断.

程序的行为未定义.