Search code examples
c++tuplesc++17structured-bindings

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?


Solution

  • 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.