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