Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Providing tuple-like structured binding access for a class

I'm trying to support tuple-like structured binding access for a class. For simplicity, I'll use the following class in the rest of this post:

struct Test
{
    int v = 42;
};

(I'm aware that this class supports structured bindings out of the box but let's assume it does not.)

To enable tuple-like access to the member of Test, we must specialize std::tuple_size and std::tuple_element:

namespace std
{

template<>
struct tuple_size<Test>
{
    static const std::size_t value = 1;
};

template<std::size_t I>
struct tuple_element<I, Test>
{
    using type = int;
};

}

And the last part we need is either Test::get<i> or a function get<i>(Test) in Test's namespace. Let's implement the latter:

template<std::size_t I>
int get(Test t)
{
    return t.v;
}

This works. However, I would like to return a reference to Test's member, just like std::get(std::tuple), for example. Therefore, I implement get as follows:

template<std::size_t I>
int& get(Test& t)
{
    return t.v;
}

template<std::size_t I>
const int& get(const Test& t)
{
    return t.v;
}

With this version, however, the following code

auto test = Test{};
auto [v] = test;

produces an error (GCC 7.1):

binding reference of type ‘std::tuple_element<0, Test>::type& {aka int&}’ to ‘const int’ discards qualifiers

So it seems as if the get<i>(const Test&) overload is selected for the structured binding. Since this overload returns a const int&, and v acts like a non-const reference to int, the code fails to compile.

According to this, however, the line auto [v] = test; should be roughly equivalent to

auto e = test;
std::tuple_element<0, Test>::type& v = get<0>(e)

Which does work since it uses the get<i>(Test&) overload.

Any ideas on why my implementation of get does not work for structured bindings?

like image 738
mtvec Avatar asked Aug 15 '17 18:08

mtvec


1 Answers

The problem is that auto [v] is a non-reference declaration, so test is copied and the copy of test is passed to get as an xvalue.

So you need to add an rvalue qualified get:

template<std::size_t I>
int&& get(Test&& t)
{
    return std::move(t.v);
}
like image 68
ecatmur Avatar answered Nov 15 '22 11:11

ecatmur