Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 333
Curious Avatar asked Aug 03 '17 20:08

Curious


People also ask

What is argument dependent lookup (ADL)?

Argument-dependent lookup, also known as ADL, or Koenig lookup [1], is the set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators.

Can ADL use a non member get function?

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.

How does ADL handle namespace-scoped friend functions?

2) namespace-scoped friend functions (and function templates) that are declared in an associated class are visible through ADL even if they are not visible through ordinary lookup 3) all names except for the functions and function templates are ignored (no collision with variables)

Does c++20 ADL work with template arguments?

Note that since C++20 ADL works also for a call to template function with explicit template arguments and the code compiles.


2 Answers

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.

like image 169
Barry Avatar answered Oct 16 '22 11:10

Barry


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

like image 12
Justin Avatar answered Oct 16 '22 12:10

Justin