Structured binding reference with tuple of reference

1.3k views Asked by At

The Structured binding Case2 in cppreference is a bit hard to understand. Basically, I want a clarification of these situations

int x = 1;
double y = 2.0;
auto [a, b] = std::forward_as_tuple(x, y);   //a, b are both reference, why?
auto&& [c, d] = std::forward_as_tuple(x, y); //What's the difference of this and above?
auto&& [e, f] = std::tuple{x, y};  //why are NOT e, f rvalue references? Resharper shows them as value type not reference type

And if there is some function return tuple of reference, how can I make a copy using structured binding?

std::tuple<int&, double&> f;
auto [x, y] = f(); //But I want a copy from the reference, how?
2

There are 2 answers

4
Barry On BEST ANSWER

std::forward_as_tuple(x, y) gives you a tuple<int&, double&>. The types of the bindings into that are int& and double& (in the same way that the types of the bindings into tuple<int, double> are int and double). Basically:

auto [a, b] = std::forward_as_tuple(x, y);
auto&& [c, d] = std::forward_as_tuple(x, y);

behaves as if:

auto __e = std::forward_as_tuple(x, y);
using __E = remove_reference_t<decltype(__e)>;
tuple_element_t<0, __E>&& a = std::get<0>(std::move(__e));
tuple_element_t<1, __E>&& b = std::get<1>(std::move(__e));

auto&& __f = std::forward_as_tuple(x, y);
using __F = remove_reference_t<decltype(__f)>;
tuple_element_t<0, F>&& c = std::get<0>(std::move(__f));
tuple_element_t<1, F>&& d = std::get<1>(std::move(__f));

So a is an rvalue reference to int& and c is an rvalue reference to double&, so int& and double& respectively. This particular formulation (I'm specifically calling it a reference to reference, rather than calling it just int&) is necessary because decltype(name) where name is a structured binding gives you the referenced type, which is why decltype(a) would give you int&.

The above also shows the difference between the [a, b] and the [c, d] case: the auto vs auto&& declaration applies to the unnamed object that we're destructuring. It does not affect the bindings themselves.

This case:

auto&& [e, f] = std::tuple{x, y};

Does not give references because it unpacks to:

auto&& __g = std::tuple{x, y};
using __G = remove_reference_t<decltype(__g)>;
tuple_element_t<0, G>&& e = std::get<0>(std::move(__g));
tuple_element_t<1, G>&& f = std::get<1>(std::move(__g));

So e is an rvalue reference to int, which means decltype(e) is int, not int&.


And if there is some function return tuple of reference, how can I make a copy using structured binding?

You can't make a copy using structured bindings. Structured bindings is solely about destructuring an object, it is not at all about changing anything. If you want to make a copy, you have to do that manually:

std::tuple<int&, double&> f = /* ... */;
std::tuple<int, double> actual_copy = f;
auto& [x, y] = actual_copy; 

In the above case, because the underlying object being destructured is an lvalue reference (auto&), this technically makes the bindings themselves lvalue references to whatever instead of rvalue references to whatever -- although I'm not sure if this is actually a meaningful distinction.

0
eerorika On

if there is some function return tuple of reference, how can I make a copy using structured binding?

Perhaps a helper function such as following would be useful:

template <class Tuple, size_t... indices>
constexpr auto
tuple_copy_impl(const Tuple& tuple, std::index_sequence<indices...>) {
   return std::tuple{std::get<indices>(tuple)...};
}

template <class Tuple>
constexpr auto
tuple_copy(const Tuple& tuple) {
    constexpr auto s = std::tuple_size_v<Tuple>;
    using I = std::make_index_sequence<s>;
    return tuple_copy_impl<Tuple>(tuple, I{});
}

auto [x, y] = tuple_copy(f());