17

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?

Curious
  • 20,870
  • 8
  • 61
  • 146
  • Note that [since C++20 ADL works also for a call to template function with explicit template arguments and the code compiles](https://stackoverflow.com/questions/45493829/why-adl-does-not-resolve-to-the-correct-function-with-stdget/63830746#63830746). – Amir Kirsh Sep 23 '20 at 18:18

3 Answers3

17

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Very interesting! I wonder though, does introducing this undefined function potentially cause any other problems? Like conflicts or the like? – Curious Aug 03 '17 at 20:29
  • @Curious It would if it ended up being preferred. I mean, don't actually do that - I just put it there for explanatory purposes. – Barry Aug 03 '17 at 20:29
  • hmmm, is there any way I can force ADL to happen? I tried the `template` keyword but of course that does not work.. – Curious Aug 03 '17 at 20:30
  • 1
    @Curious Outside of structured bindings, nope. Also, I swear there was a language proposal to make this just work but I can't find. – Barry Aug 03 '17 at 20:33
  • 1
    @Barry, it is probably Robert Haberlach's D0389R1 that you remebered, but it was replaced with [p0846r0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0846r0.html) which was accepted into C++20, which now [makes the OP's code valid](https://stackoverflow.com/questions/45493829/why-adl-does-not-resolve-to-the-correct-function-with-stdget/63830746#63830746) – Amir Kirsh Sep 10 '20 at 13:33
12

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

Justin
  • 24,288
  • 12
  • 92
  • 142
  • Would it work to say `std::cout << template get<0>(tup) << '\n'`, or does a disambiguating `template` not work there for some reason? – Daniel H Aug 03 '17 at 20:43
  • @DanielH That sounds like it *could* work if someone proposed it. It [doesn't work at the moment](https://wandbox.org/permlink/dmG6fVeDQwzHvl6q) – Justin Aug 03 '17 at 20:47
  • Thanks for the answer. I want to accept both answers but since the other answer includes a straightforward example of a simple resolution technique. I am going to accept that. – Curious Aug 04 '17 at 17:44
2

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!

Amir Kirsh
  • 12,564
  • 41
  • 74