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

C++ template overload resolution called from template function pick candidate declared after template instantiation

这是模板上下文中非常奇怪的重载解析的极简示例:

#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}; }

这似乎与 ADL 有关,因为如果将 I 放在命名空间中,总是会调用 "overload 1"。

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

For a dependent name used in a template definition, the lookup is postponed until the template arguments are known, at which time ADL examines function declarations with external linkage (until C++11) that are visible from the template definition context as well as in the template instantiation context, while non-ADL lookup only examines function declarations with external linkage (until C++11) that are visible from the template definition context. http://en.cppreference.com/w/cpp/language/unqualified_lookup#Template_definition

但是这里 "Overload 2" 是在 main 之后声明的! maingf 的实例化点,我假设只有在 之前 main 声明的函数是重载候选者。

请注意,此行为与 g 作为模板有关,因为 hg 的等价物不是模板函数)调用 "Overload 1".

"overload 2" - 在 main 之后声明 - 如何被调用?

此行为已通过 clang++ (3.8.1) 和 g++ (6.2.1) 重现。

[temp.dep.candidate]/1:

If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

此外,[temp.point]/6,强调我的:

A specialization for a function template [...] may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. [...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule ([basic.def.odr]), the program is ill-formed, no diagnostic required.

您的程序的行为未定义。