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?
A structured binding declaration introduces all identifiers in the identifier-list as names in the surrounding scope and binds them to subobjects or elements of the object denoted by expression. The bindings so introduced are called structured bindings.
What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.
When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.
std::forward has a single use case: to cast a templated function parameter (inside the function) to the value category (lvalue or rvalue) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues, and lvalues to be passed on as lvalues, a scheme called “perfect forwarding.”
Here is general solution to get what you want. It is a variation on std::forward
that permits its template argument and its function argument to have unrelated types, and conditionally casts its function argument to an rvalue iff 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 "aliasing constructor" of std::shared_ptr
.
You could use it like this:
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
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);
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