I have a struct
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 struct if it is called with an lvalue DemoStruct<T>
and a rvalue reference to the I
-th member of the struct if it is called with an rvalue DemoStruct<T>
.
My current implementation looks like this
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 doesn't do what I expected, and always returns an rvalue-reference to T
instead.
Here is a wandbox that shows the problem.
What is the correct way to return references to the struct members preserving the value category of the struct passed into the function?
EDIT:
As Kinan Al Sarmini pointed out, auto&& [a, b] = ...
indeed deduces the types for a
and b
to be non-reference types. This is also true for std::tuple
, e.g. both
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 though std::get<0>(my_tuple)
returns references, as shown by
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 defect, intended or a bug in both GCC and Clang?
The behavior is correct.
decltype
applied to a structured binding returns the referenced type, which for a plain struct is the declared type of the data member referred to (but decorated with the cv-qualifiers of the complete object), and for the tuple-like case is "whatever tuple_element
returned for that element". This roughly models the behavior of decltype
as applied to a plain class member access.
I cannot currently think of anything other than manually computing the desired type, i.e.:
using fwd_t = std::conditional_t<std::is_lvalue_reference_v<T>,
decltype(a)&,
decltype(a)>;
return std::forward<fwd_t>(a);