I'm learning about structured binding declarations. My understanding was that in auto& [x, y] = expr; variables x and y are introduced of types "reference to std::tuple_element<i, E>::type" (for i=0, 1 and E is the type of the invisible variable e). Moreover, these variables are initialized with get<i>(e).
So, if I use auto& and get<> returns a value (not a reference), it should not compile, as you cannot bind an lvalue to a temporary. However, the following example builds for me in some versions of GCC, Clang, and Visual Studio:
#include <cstddef>
#include <tuple>
#include <type_traits>
struct Foo {
template<std::size_t i>
int get() { return 123; }
};
namespace std {
template<> struct tuple_size<Foo> : integral_constant<size_t, 1> {};
template<std::size_t i> struct tuple_element<i, Foo> { using type = int; };
}
int main() {
Foo f;
auto& [x] = f;
x++;
}
Moreover, C++ Insights clearly shows that clang expands the structured binding to:
Foo f = Foo();
Foo & __f17 = f;
std::tuple_element<0, Foo>::type x = __f17.get<0>();
x++;
Here, it declares x not as a reference, but as a value. Why is that?
I expected lvalue references and compilation error: e (__f17 in the example above) is an lvalue reference.
That is because auto& does not apply to the structured bindings. It is applied to the underlying entity that refers to the structure. In your cppinsights snippet, that would be __f17.
If you were to use auto [x] instead, the snippet would expand to something like this
Foo f = Foo();
Foo __f17 = f; // Difference here
std::tuple_element<0, Foo>::type x = __f17.get<0>();
x++;
The bindings themselves are always a sort of reference into an underlying object. The cppinsights code doesn't accurately represent that however. The relevant passages in the C++ standard say this
[dcl.struct.bind]
3 Otherwise, if the qualified-id
std::tuple_size<E>names a complete type, the expressionstd::tuple_size<E>::valueshall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression. The unqualified-idgetis looked up in the scope ofEby class member access lookup, and if that finds at least one declaration, the initializer ise.get<i>(). Otherwise, the initializer isget<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 ] In either case,eis an lvalue if the type of the entityeis an lvalue reference and an xvalue otherwise. Given the typeTidesignated bystd::tuple_element<i, E>::type, eachviis a variable of type “reference toTi” initialized with the initializer, where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise; the referenced type isTi.
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