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.


Argument Dependent Lookup doesn't work the same way for function templates where an explicit template argument is given.

Although a function call can be resolved through ADL even if ordinary lookup finds nothing, a function call to a function template with explicitly-specified template arguments requires that there is a declaration of the template found by ordinary lookup (otherwise, it is a syntax error to encounter an unknown name followed by a less-than character)

Basically, there needs to be some way for the unqualified lookup to find a template function. Then, the ADL can kick in (because the name get is then known to be a template). Cppreference gives an example:

namespace N1 {
  struct S {};
  template<int X> void f(S);
}
namespace N2 {
  template<class T> void f(T t);
}
void g(N1::S s) {
  f<3>(s);      // Syntax error (unqualified lookup finds no f)
  N1::f<3>(s);  // OK, qualified lookup finds the template 'f'
  N2::f<3>(s);  // Error: N2::f does not take a non-type parameter
                //        N1::f is not looked up because ADL only works
                //              with unqualified names
  using N2::f;
  f<3>(s); // OK: Unqualified lookup now finds N2::f
           //     then ADL kicks in because this name is unqualified
           //     and finds N1::f
}

Structured bindings are a special case, with ADL enabled.

In the following contexts ADL-only lookup (that is, lookup in associated namespaces only) takes place:

  • the lookup of non-member functions begin and end performed by the range-for loop if member lookup fails
  • the dependent name lookup from the point of template instantiation.
  • the lookup of non-member function get performed by structured binding declaration for tuple-like types

Emphasis added