Perfect forwarding of variables declared with structured binding

Viewed 675

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?

2 Answers
Related