I've used std::tie
without giving much thought into it. It works so I've just accepted that:
auto test()
{
int a, b;
std::tie(a, b) = std::make_tuple(2, 3);
// a is now 2, b is now 3
return a + b; // 5
}
But how does this black magic work? How does a temporary created by std::tie
change a
and b
? I find this more interesting since it's a library feature, not a language feature, so surely it is something we can implement ourselves and understand.
In order to clarify the core concept, let's reduce it to a more basic example. Although std::tie
is useful for functions returning (a tuple of) more values, we can understand it just fine with just one value:
int a;
std::tie(a) = std::make_tuple(24);
return a; // 24
Things we need to know in order to go forward:
std::tie
constructs and returns a tuple of references.std::tuple<int>
and std::tuple<int&>
are 2 completely different classes, with no connection between them, other that they were generated from the same template, std::tuple
.tuple has an operator=
accepting a tuple of different types (but same number), where each member is assigned individually—from cppreference:
template< class... UTypes > tuple& operator=( const tuple<UTypes...>& other );
(3) For all i, assigns
std::get<i>(other)
tostd::get<i>(*this)
.
The next step is to get rid of those functions that only get in your way, so we can transform our code to this:
int a;
std::tuple<int&>{a} = std::tuple<int>{24};
return a; // 24
The next step is to see exactly what happens inside those structures.
For this, I create 2 types T
substituent for std::tuple<int>
and Tr
substituent std::tuple<int&>
, stripped down to the bare minimum for our operations:
struct T { // substituent for std::tuple<int>
int x;
};
struct Tr { // substituent for std::tuple<int&>
int& xr;
auto operator=(const T& other)
{
// std::get<I>(*this) = std::get<I>(other);
xr = other.x;
}
};
auto foo()
{
int a;
Tr{a} = T{24};
return a; // 24
}
And finally, I like to get rid of the structures all together (well, it's not 100% equivalent, but it's close enough for us, and explicit enough to allow it):
auto foo()
{
int a;
{ // block substituent for temporary variables
// Tr{a}
int& tr_xr = a;
// T{24}
int t_x = 24;
// = (asignement)
tr_xr = t_x;
}
return a; // 24
}
So basically, std::tie(a)
initializes a data member reference to a
. std::tuple<int>(24)
creates a data member with value 24
, and the assignment assigns 24 to the data member reference in the first structure. But since that data member is a reference bound to a
, that basically assigns 24
to a
.