Perfect forwarding variables declared using structured binding

I have a structure

template <typename T> struct Demo { T x; T y; }; 

and I'm trying to write a generic function similar to std::get for tuples that takes a compile-time index I and returns an lvalue reference to the I th member of the structure if it is called with an lvalue DemoStruct<T> and a rvalue reference to the I th member of the structure if it is called with rvalue DemoStruct<T> .

My current implementation looks like

 template <size_t I, typename T> constexpr decltype(auto) struct_get(T&& val) { auto&& [a, b] = std::forward<T>(val); if constexpr (I == 0) { return std::forward<decltype(a)>(a); } else { return std::forward<decltype(b)>(b); } } 

However, this does not do what I expected, and always returns an rvalue reference to T

Here is a wandbox that shows the problem.

What is the correct way to return references to structure elements that preserve the category of structure values ​​passed to the function?

EDIT: As Keenan Al Sarmini noted, auto&& [a, b] = ... really infers the types for a and b as non-basic types. This is true for std::tuple , for example. and

 std::tuple my_tuple{std::string{"foo"}, std::string{"bar"}}; auto&& [a, b] = my_tuple; static_assert(!std::is_reference_v<decltype(a)>); 

and

 std::tuple my_tuple{std::string{"foo"}, std::string{"bar"}}; auto&& [a, b] = std::move(my_tuple); static_assert(!std::is_reference_v<decltype(a)>); 

compile fine even if std::get<0>(my_tuple) returns links as shown

 std::tuple my_tuple{3, 4}; static_assert(std::is_lvalue_reference_v<decltype(std::get<0>(my_tuple))>); static_assert(std::is_rvalue_reference_v<decltype(std::get<0>(std::move(my_tuple)))>); 

Is this a language flaw, intended, or a bug in both GCC and Clang?

+7
c ++ language-lawyer perfect-forwarding c ++ 17 structured-bindings
source share
2 answers

The behavior is correct.

decltype , applied to structured binding, returns a reference type, which for a simple structure is the declared type of the mentioned data element (but decorated with cv-qualifiers of the full object), and for a tuple - a similar case - "any tuple_element returned for this element". This roughly simulates decltype behavior decltype respect to accessing elements of a simple class.

Currently, I can’t think of anything other than manually calculating the desired type, that is:

 using fwd_t = std::conditional_t<std::is_lvalue_reference_v<T>, decltype(a)&, decltype(a)>; return std::forward<fwd_t>(a); 
+6
source share

Here is a general solution to get what you want. This is an option on std::forward , which allows the template argument and its function argument to have unrelated types and conditionally pass the argument to its rvalue function if its template argument is not an lvalue reference.

 template <typename T, typename U> constexpr decltype(auto) aliasing_forward(U&& obj) noexcept { if constexpr (std::is_lvalue_reference_v<T>) { return obj; } else { return std::move(obj); } } 

I named it aliasing_forward as a nod to the "alias constructor" std::shared_ptr .

You can use it as follows:

 template <size_t I, typename T> constexpr decltype(auto) struct_get(T&& val) { auto&& [a, b] = val; if constexpr (I == 0) { return aliasing_forward<T>(a); } else { return aliasing_forward<T>(b); } } 

Demo

+3
source share