Is there a one-liner to unpack tuple/pair into references?
Asked Answered
J

3

52

I frequently write snippets like

int x,y,z; tie(x,y,z) = g[19];

where, for instance, g was declared earlier

vector<tuple<int,int,int>> g(100);

Problem is, maybe later I actually want x and y to point to the internals of g by reference, and the refactoring is ugly, e.g.

int &x = get<0>(g[19]);
int &y = get<1>(g[19]);
int &z = get<2>(g[19]);

or sometimes even worse, for instance if the access is a more complex expression

tuple<int,int,int> &p = g[19]; // if the rhs was actually more complicated
int &x = get<0>(p);
int &y = get<1>(p);
int &z = get<2>(p);

Is there a better refactoring, more in the style of the assignment to tie(..)?

The difficulty as I understand it is that references insist on being initialized exactly at their declaration. So, in possibly other words, is there a way to use tie-like syntax for multiple variable initialization in c++ (this would also make the earlier non reference usage cleaner)?

Juvenescence answered 17/6, 2016 at 8:6 Comment(6)
Interesting, but I guess it's about time for you to define a named struct to replace tuple<int, int, int> ...Wayland
There is this paper: isocpp.org/files/papers/P0144R1.pdf (dunno if a more recent version exists)Willy
Fair enough. But refactoring the tuple into a named struct both adds a name to the global namespace (unless we play more games, which to me makes the intent less clear, not more), and removes other conveniences to tuple, such as a default operator< .Juvenescence
@Hiura: wg21.link/p0144 for the latest revision. P0217 contains the wording.Fearless
@KerrekSB thanks for the links!Willy
@Hiura: Don't get your hopes up just yet, the proposal is somewhat controversial. Watch out for news in the next few weeks.Fearless
U
74

Fortunately, C++17 has a solution for exactly this problem, the structured binding declaration. Even the non-reference interface can be improved.

auto[x, y, z] = g[i];

The above line declares x, y,z and initializes them with the values of g[i]. Not only is it cleaner, but it could be more efficient for types that are expensive to construct.

To get references to the members of g[i], one can write

auto& [x, y, z] = g[i];
Ulrich answered 24/8, 2017 at 15:49 Comment(5)
@StevenVascellaro The two lines I wrote (after fixing the typo of using parentheses instead of brackets) are an example of how a structured binding declaration would be used to get the values or references out of an std::tuple<int, int, int>. The page I linked to also has 3 examples with comprehensive explanations. I can't come up with a better example for this. Is there something in particular you find confusing in my answer?Ulrich
Do x, y, and z need to be declared and assigned values individually?Remuneration
@StevenVascellaro They do not. This is the whole point of this feature. They are declared and initialized in a single line. eg in the first example, x has been declared and copy-constructed from the first element of g[i].Ulrich
So if I wanted to initialize a set of integers, would I do something like int num[3] = {1, 2, 3}; auto[x, y, z] = num;?Remuneration
@Remuneration In case you didn't get an answer to this and to provide closure, yes that works.Shandeigh
L
7

You can automate it with a function so that you don't have to type out 3 (or more) lines:

template <class... Ts, std::size_t... Is, class Tuple>
decltype( auto ) tie_from_specified( std::index_sequence<Is...>, Tuple& tuple )
{
    return std::tuple<Ts...>{ std::get<Is>( tuple )... };
}

template <class... Ts, class Tuple>
decltype( auto ) tie_from( Tuple& tuple )
{
    return tie_from_specified<Ts...>( std::make_index_sequence<sizeof...( Ts )>{}, tuple );
}

Usage would be:

int x{ 2 };
std::tuple<int, int&, int> g19( 1, x, 3 );

// new tuple: ref to get<0>( g19 ), value of get<1>( g19 ), ref to get<2>( g19 )
auto t0{ tie_from<int&, int, int&>( g19 ) };

// new tuple: ref to get<0>( g19 ), ref to get<2>( g19 )
auto t1{ tie_from_specified<int&, int&>( std::index_sequence<0, 2>{}, g19 ) };
Lungworm answered 17/6, 2016 at 10:50 Comment(1)
This seems to be the best solution in the meantime given the current standard. We'll see what the future holds with C++17.Juvenescence
A
0

You could write a struct to store the references:

struct vector3d
{
    vector3d(std::tuple<int, int, int>& tuple)
      : x(std::get<0>(tuple)),
        y(std::get<1>(tuple)),
        z(std::get<2>(tuple))
    {}

    int& x;
    int& y;
    int& z;
};

And then use it like this:

vector3d vec(g[6]);
// do sth with vec.x or vec.y or vec.z or all together :)
Asafetida answered 17/6, 2016 at 8:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.