Why doesn't ADL find function templates?
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
}
Hidden friend template function not found by ADL
get_if<int>(f);
the problem is here, you tried to perform the instantiation by specifying the type, which needs the name of get_if<int>
before the parameter is known
namespace Bar {
template<typename...Types>
class Foo {
public:
template<class T>
friend int* get_if(T a, Foo<Types...>& f) {
return nullptr;
}
};
}
int main() {
Bar::Foo<int, double> f;
get_if(5, f);
return 0;
}
in contrast, this works.
Argument-dependent lookup and function templates
Argument-dependent lookup works for unqualified function call expressions. This is true for "normal" functions just as well as for function template specializations.
However, when you provide explicit template parameter for a template function, then the expression does not syntactically look like an function call:
foo<3>(x) // "foo less than three?"
That's why those cases don't trigger ADL. However, once a name is known to be a template, ADL does apply!
template <int> void foo();
foo<double, 5, T>(x); // uses ADL
Why ADL does not resolve to the correct function with std::get
The problem ultimately is templates:
std::cout << get<0>(tup) << std::endl;
// ~~~~
At that point, the compiler doesn't know that this is a function that needs to be looked up using ADL yet - get
is just a name. And since that name by itself doesn't find anything, this is going to be interpreted as an unknown name followed by less-than. To get this to work, you need some other function template get
visible:
using std::get;
std::cout << get<0>(tup) << std::endl; // now, OK
Even if it does nothing:
template <class T> void get();
int main() {
auto tup = std::make_tuple(1, 2);
std::cout << get<0>(tup) << std::endl;
}
The structured binding wording explicitly looks up get
using argument-dependent lookup, so it avoids the need to have an already-visible function template named get
, from [dcl.struct.bind]:
The unqualified-id
get
is looked up in the scope ofE
by class member access lookup, and if that finds at least one declaration, the initializer ise.get<i>()
. Otherwise, the initializer isget<i>(e)
, whereget
is looked up in the associated namespaces. In either case,get<i>
is interpreted as a template-id. [ Note: Ordinary unqualified lookup is not performed. — end note ]
The note is the key. If we had performed unqualified lookup, we'd just fail.
ADL cannot find overloaded function
The solution is to forward declare is_valid(int)
:
bool is_valid(int);
template<typename T>
struct S
{
bool valid(T a)
{ return is_valid(a); }
};
ADL for fundamental types (like int
) produces an empty set of namespaces and classes to consider, so when you pass 0
into S::valid
, you're not bringing in the outer is_valid(int)
. Forward declaration effectively lets the template know the function exists.
Regarding the behavior you see in Godbolt... there must be some extra work being done by the compiler explorer because the same gcc and clang versions it is allegedly using do not work on any other compiler (like Wandbox)
If you really want ADL to work, then you need to modify the free function is_valid
such that ADL is an option. My recommendation is to declare a helper struct ADL_Helper
in the same scope as all your free-floating is_valid
functions, then S::is_valid
will pass an instance:
struct ADL_Helper{};
template<typename T>
struct S
{
bool valid(T a)
{ return is_valid(a, ADL_Helper{}); }
};
bool is_valid(int, ADL_Helper)
{ return true; }
int main()
{
S<int> s;
s.valid(0);
}
Demo
Dependent template names and C++20 ADL
This is P0846, which is a C++20 feature. It appears that gcc does not implement this yet.
It's not a question of dependent name or not, it's a question of does the compiler know that foo
refers to a template or not, and so is foo<
doing a comparison or is it starting to do template parameters.
In C++17, the compiler had to already know that foo
was a template-name (which you could accomplish by adding using N::foo;
) in order to perform ADL, in C++20 that's no longer true - now the rule is that if unqualified lookup finds a template or nothing, we also consider it to be a template.
The dependence of foo
didn't change as a result of this paper. In foo<0>(c);
, foo
is still a dependent name. The rule in [temp.dep] from C++17 was:
In an expression of the form:
postfix-expression ( expression-listopt )
where the postfix-expression is an unqualified-id, the unqualified-id denotes a dependent name if
- any of the expressions in the expression-list is a pack expansion,
- any of the expressions or braced-init-lists in the expression-list is type-dependent, or
- the unqualified-id is a template-id in which any of the template arguments depends on a template parameter.
The second bullet applies here - c
is type-dependent. The C++20 wording is the same. The issue here wasn't that foo
wasn't dependent in C++17. It's just that the rule was when we foo<
, we don't know that foo
is a template, so it's considered the less-than-operator, and that then fails.
Related Topics
How to Output Coloured Text to a Linux Terminal
Convert String to Variable Name or Variable Type
How to Read a File Line by Line or a Whole Text File At Once
Best C++ Ide or Editor For Windows
Accessing Arrays by Index[Array] in C and C++
Return Statement VS Exit() in Main()
What Open Source C++ Static Analysis Tools Are Available
Is This Behavior of Vector::Resize(Size_Type N) Under C++11 and Boost.Container Correct
Py_Initialize Fails - Unable to Load the File System Codec
How to Initialize Base Class Member Variables in Derived Class Constructor