14

The most recent draft of the structured bindings proposal (on which the C++17 feature was based) requires std::tuple_size, member get or std::get, and std::tuple_element. Previous drafts require only std::tuple_size and member get or std::get. As far as I can tell, there was no discussion on adding this, it just appeared in the final draft. Is there a compelling reason to require the tuple_element specialization, considering I believe it can be implemented in general as

template<std::size_t index, typename T>
struct tuple_element {
    using type = decltype(std::get<index>(std::declval<T>()));
};

Does anyone know of why this requirement was added?

David Stone
  • 26,872
  • 14
  • 68
  • 84
  • This might be needed for perfect forwarding, `std::get` returns `std::tuple_element_t >&` or `std::tuple_element_t >&&` – NathanOliver Apr 27 '18 at 15:05
  • 2
    Your implementation is broken. For example, `std::get` returns a reference type with `std::tuple` regardless if the element is a reference or not. Worse, the reference depends on the value category of the `std::tuple` – Passer By Apr 27 '18 at 15:06
  • I think it should be `get` instead of `std::get` in your implementation to make use of ADL. – xskxzr Apr 28 '18 at 03:17

1 Answers1

6

Consider the case:

std::tuple<int, int&>& foo();
auto& [x, y] = foo();

What is decltype(x) and what is decltype(y)? The goal of the language feature is that x just be another name for foo().__0 and y be another name for foo().__1, which means that they should to be int and int&, respectively. As specificied today, this unpacks into:

auto& __e = foo();
std::tuple_element_t<0, decltype(__e)>& x = std::get<0>(__e);
std::tuple_element_t<1, decltype(__e)>& y = std::get<1>(__e);

And the rules work such that decltype(x) is the type to which x refers, so int. And decltype(y) is the type to which y refers, so int&.

If we avoided tuple_element, by doing something like:

auto&& x = std::get<0>(__e);
auto&& y = std::get<1>(__e);

Then we couldn't differentiate between x and y, because there is no way to differentiate between what std::get<0>(__e) and std::get<1>(__e) do: both give back an int&.

This is also the way to add consistency between the above case and the normal struct case:

struct C {
    int i;
    int& r;
};
C& bar();
auto& [a, b] = bar();

We want, for the purposes of structured bindings, for a and b here to behave the same way as x and y there. And a and b here aren't introduced variables, they're just different names for __e.i and __e.r.


In the non-reference case, there is a different scenario where we cannot differentiate:

std::tuple<int, int&&> foo();
auto [x, y] = foo();

Here, we at present unpack via:

auto __e = foo();
std::tuple_element_t<0, decltype(e)>& x = std::get<0>(std::move(__e));
std::tuple_element_t<1, decltype(e)>& y = std::get<1>(std::move(__e));

Both std::get calls return an int&&, so you couldn't differentiate between them using auto&&... but the results of tuple_element_t are different - int and int&&, respectively. This difference could be seen with the normal struct case too.


Note that due to CWG 2313, actually the unpacking happens into a uniquely named variable reference and the identifiers specified into the binding just refer to those objects.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Actually, it doesn't unpack into what you said. It used to be like that, but after a DR it changed to what you're saying, but the variables are `__x` and `__y` (or whatever) and `x` and `y` refer to the object bound to those variables. Just to be pedantic :) – Rakete1111 Apr 27 '18 at 16:05
  • [CWG 2313](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2313) for the curious. I think I'll keep the answer as-is, I think it at least conveys the idea @Rakete1111 – Barry Apr 27 '18 at 16:24
  • `decltype(x)` is also `int&` in your first example. – xskxzr Apr 27 '18 at 17:46
  • @Barry Oh sorry, I mean `x` in `std::tuple_element_t<0, decltype(__e)>& x = std::get<0>(__e);`. – xskxzr Apr 27 '18 at 17:50
  • @xskxzr Yeah, that's just exposition, but I'm assuming we're still following the same rules as to how to determine what `decltype(x)` means. – Barry Apr 27 '18 at 17:52
  • 1
    For your example `__e` is actually cast to an xvalue for `get`, so you can differentiate `int` and `int&` from the return type of `get` - but not `int` and `int&&`. – T.C. Apr 27 '18 at 23:56