为什么 ADL 不能找到函数模板?

C + + 规范的哪一部分限制了在相关名称空间集合中查找函数模板的依赖参数查找?换句话说,为什么下面 main中的最后一个调用无法编译?

namespace ns {
struct foo {};
template<int i> void frob(foo const&) {}
void non_template(foo const&) {}
}


int main() {
ns::foo f;
non_template(f); // This is fine.
frob<0>(f); // This is not.
}
5542 次浏览

This part explains it:

C++ Standard 03 14.8.1.6:

[Note: For simple function names, argument dependent lookup (3.4.2) applies even when the function name is not visible within the scope of the call. This is because the call still has the syntactic form of a function call (3.4.1). But when a function template with explicit template arguments is used, the call does not have the correct syntactic form unless there is a function template with that name visible at the point of the call. If no such name is visible, the call is not syntactically well-formed and argument-dependent lookup does not apply. If some such name is visible, argument dependent lookup applies and additional function templates may be found in other namespaces.

namespace A {
struct B { };
template<int X> void f(B);
}
namespace C {
template<class T> void f(T t);
}
void g(A::B b) {
f<3>(b);    //ill-formed: not a function call
A::f<3>(b); //well-formed
C::f<3>(b); //ill-formed; argument dependent lookup
// applies only to unqualified names
using C::f;
f<3>(b);    //well-formed because C::f is visible; then
// A::f is found by argument dependent lookup
}

Edit: No, this is not right. See @Kornel's answer.


I'm not entirely sure but having consulted Stroustrup's "The C++ programming language" I think that Appendix C section 13.8.4 might be the cause.

Since frob is a template one could conceivably specialise it for i=0 at a point after you call it. This means that the implementation would be left with two possible ways of choosing which frob to call as it appears it can choose it at the point of instantiation or at the end of processing the translation unit.

So, I think the problem is you could do

namespace ns {
struct foo {};
template<int i> void frob(foo const&) {}
}


int main() {
ns::foo f;
frob<0>(f);
return 0;
}


namespace ns {
template<> void frob< 0 >(foo const&) { /* Do something different*/ }
}

I would like to refine slightly accepted answer. It is not clear in the OP question, but the important part from the standard (cited by Kornel) is this (emphasis mine):

But when a function template with explicit template arguments is used, the call does not have the correct syntactic form

so what is prohibited is relying on ADL and using explicit template arguments. Unfortunately using non-type template arguments requires using explicit arguments (unless they have default values).

Below is sample code showing this.:

[live]

#include <string>
#include <utility>


namespace C {
struct B { };
template<class T> void f(T t){}
}


void g(C::B b) {
f(b);           // OK
//f<C::B>(b);   // ill-formed: not a function call, but only
//  because explicit template argument were used


std::string s;
move(s);                      // OK
//move<std::string&>(s);      // Error, again because
//  explicit template argument were used
std::move<std::string&>(s);   // Ok
}


int main()
{
C::B b;
g(b);
}

Since c++20, adl works also fine with explicit function template. Here is the proposal: P0846R0: ADL and Function Templates that are not Visible:

Instead of requiring the user to use the template keyword, a revision to the lookup rules was proposed so that a name for which a normal lookup produces either no result or finds one or more functions and that is followed by a a "<" would treated as if a function template name had been found and would cause ADL to be performed.

Currently, only GCC 9 has implement this feature, so your example can compile.

live demo.