6

C++17 introduced structured binding declarations: auto [a, b] = some_tuple;.

This works out of the box for things like std::tuple. It is also possible to make it work for custom types, you just have to provide (among other things) an get-function template, either as member or outside the custom class.

For the standard classes, this is done via a non-member get lying in the std-namespace: auto a = std::get<0>(some_tuple); works, but not auto a = some_tuple.get<0>();.

But here it gets weird for me: Since we have to explicitly specify the template parameter N for get, ADL does not work, for example, we can't just write auto a = get<0>(some_tuple);. But then the structured binding declaration with tuples shouldn't work too, because it's just syntactic sugar for calls like either get<N>(some_tuple) or some_tuple.get<N>() (modulo some &)! And indeed, when I provide only a non-member version of get for my custom class inside a namespace, it doesn't work! EDIT: Structured binding for custom classes also works fine, see the code snippet in the accepted answer for a minimal example!

So how do the implementers of the standard make structured binding work for e.g. tuples without a get as member, and how can I achieve the same behavior for my custom classes?

x432ph
  • 400
  • 1
  • 10
  • 1
    "*So how do the implementers of the standard make structured binding work*" It's the *compiler*; it gets to do whatever it wants. If the standard says that X happens, then the compiler makes X happen, period. – Nicol Bolas Mar 23 '19 at 17:39
  • @NicolBolas Really? I always thought the standard libraries are built on top of the compiler, so that in principle everyone could code their own implementation on top of an existing compiler. – x432ph Mar 24 '19 at 02:02
  • 1
    Most of the C++ standard library is implementable in C++. But the behavior of structured binding is defined by the *language*. The language may be *calling* standard library constructs, but what `auto [x, y] = ...;` does is governed by the compiler. It's the compiler that selects what function gets called, not the library. – Nicol Bolas Mar 24 '19 at 02:07
  • Yes, that I understand. But the question is: Does the compiler know that the get in std is meant because they added some special cases to the language, or did they something else I could do myself? For example, if I copied the whole library and changed std to mystd, would it still work? – x432ph Mar 24 '19 at 06:35
  • 1
    "*if I copied the whole library and changed std to mystd, would it still work?*" Yes, but that would be because you'd be changing `std::tuple` into `mystd::tuple`, with a `mystd::get` in the same namespace. The structured binding machinery isn't linked to the namespace `std`; the system searches the namespace associated with the type in question. That very specific kind of search (look up all functions with this name in namespace `X`, but *only there*) is not something you can (easily) write. – Nicol Bolas Mar 24 '19 at 13:20
  • Okay, I think I get it now. I was confused at first, because it didn't work for my custom class at first, but now it does, so I probably had some other problem in the code. – x432ph Mar 24 '19 at 16:41

1 Answers1

7

They cheat.

But you can emulate their cheating by adding a template get to the global namespace.

template<class T, std::enable_if_t<std::is_same<T,void>{}, bool>>
void get(int)=delete;

which should activate "parse get as a template".

You don't need to do this to get structured bindings working. As noted, the compiler just cheats:

namespace example {
    struct silly {
        int x;
    };
    template<std::size_t I>
    int& get( silly& s ) { return s.x; }
}
namespace std {
    template<>
    struct tuple_size<::example::silly>:std::integral_constant<std::size_t, 1>{};
    template<>
    struct tuple_element<0, ::example::silly>{ using type=int; };
}

int main() {
    example::silly s { 42 };

    auto&& [x] = s;
    std::cout << x;
}
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    AFAIK you don't need this hack pretty much always in C++20 anymore: http://eel.is/c++draft/basic.lookup.unqual#3 – Rakete1111 Mar 23 '19 at 23:00
  • Hm, interesting trick to enable ADL for templated functions, however, this isn't really how std::get behaves, because I can't omit the std::, so ADL does not (always) work there. Or maybe I ask another way: In the end, what is different when using the above hack instead of just defining the real get in global namespace to begin with? Just that I can acces get with and without specifying the namespace now? – x432ph Mar 23 '19 at 23:11
  • (Although, ironically, the above hack of course also enables ADL for std::get.) – x432ph Mar 23 '19 at 23:30
  • 1
    @x432ph I'm unclear in what scenario "doesn't work" or "I cannot omit the `std::`". A get in the global namespace will not be found via ADL, so won't work for structured bindings, nor will it work in a namespace with a different `get` defined. "And indeed, when I provide only a non-member version of get for my custom class inside a namespace, it doesn't work!" -- again, structured bindings work without any trick like the above. The above trick (in pre-[tag:C++20]) simply convinces the compiler to parse `get<` as a template and not a less than expression. – Yakk - Adam Nevraumont Mar 24 '19 at 00:06
  • @Yakk-AdamNevraumont Sorry, I think my formulation was not the best. All I wanted to say: Without this trick, get<0>(tuple) is not parsed as template, but structured binding for tuples works. Hence the implementers of the standard cheat in another way than with this trick. Which of course, you never denied. I just wanted to make sure that this reasoning is correct. – x432ph Mar 24 '19 at 01:59
  • 1
    @x432ph yes, they cheat in an arbitrary way. They simply invoke ADL on something. The implementors are the compiler. – Yakk - Adam Nevraumont Mar 24 '19 at 08:48
  • @Yakk-AdamNevraumont The code sample in your edited answer just made me realize that I must have been confused before, because structured binding did not work for me when I tried it without the trick. But now it does, so I had some different problem in my code. I will edit my question now to correct that. – x432ph Mar 24 '19 at 16:44