Curious
Curious

Reputation: 21540

Why ADL does not resolve to the correct function with std::get

I am trying to code a template function that uses an ADL resolved get to fetch members of a struct/range (tuple-esque).

#include <iostream>
#include <utility>
#include <tuple>

int main() {
    auto tup = std::make_tuple(1, 2);
    std::cout << get<0>(tup) << std::endl;
}

I am doing this because of what the structured bindings proposal (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf §11.5.3) says about how get is used to fetch elements from the struct. It says that a non member get is used to fetch elements from within the struct.

I assumed that the code above would compile, because ADL would cause the get function to be looked for in the std namespace (because it's argument is of type std::tuple<int, int>, which is in std), where it would be found. But, I get an error. Can someone explain the right approach here and also why the code above does not work? How can one force ADL to happen in this case?

Upvotes: 17

Views: 869

Answers (3)

Amir Kirsh
Amir Kirsh

Reputation: 13810

Fast forward to C++20

p0846r0 that was accepted into C++20 now allows ADL for a call to template function with explicit template arguments.

So the OP's code now compiles as is with C++20 without an error!

Upvotes: 2

Justin
Justin

Reputation: 25347

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

Upvotes: 12

Barry
Barry

Reputation: 303457

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.

Upvotes: 17

Related Questions