Why Doesn't Adl Find Function Templates

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 of E by class member access lookup, and if that finds at least one declaration, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e), where get 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



Leave a reply



Submit