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?
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.
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.
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)
Note that since C++20 ADL works also for a call to template function with explicit template arguments and the code compiles.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With