When accessing to an element of a given compound type, you probably want to get its internal fields in order to work with them.
When tuples were introduced (my post about tuples), the way of doing this was similar to this:
std::tuple<int, std::string, std::string> person { 20050316, "Isabel", "Pantoja" };
int birth_date = std::get<0>(person);
std::string& first_name = std::get<1>(person);
std::string& last_name = std::get<2>(person);
In this way, you were able to access any elements inside the tuple. Quite useful, but quite verbose as well.
So, std::tie
could help us to make this code by far easier to read:
std::tuple<int, std::string, std::string> person { 20050316, "Isabel", "Pantoja" };
int birth_date;
std::string first_name;
std::string last_name;
std::tie(birth_date, first_name, last_name) = person;
The magic that std::tie
does here is extracting all elements inside of the tuple and mapping them to the values passed as non-const-references to the std::tie
function.
C++17 introduced “structured binding”, a far better and evident and elegant way of doing this without getting lost in what std::tie
or std::get<N>
do.
Structured binding, as its name suggests, binds a set of variables to its corresponding values inside a tuple. Actually these constructs are supported:
std::tuple<T...>
std::pair<T, U>
std::array<T, N>
and stack-based old-style arrays- Structs!
How does it work?
The example above could be re-written like this:
std::tuple<int, std::string, std::string> person { 20050316, "Isabel", "Pantoja" };
auto& [birth_date, first_name, last_name] = person;
So, the compiler will “decompose” person in its “atomic” values and will bind the birth_date
reference to the first value in person
, the first_name
to the second value and the last_name
to the third one.
Notice that birth_date
, first_name
, and last_name
are references because of auto&
. If I would have used auto
instead, they would have been actual values instead.
And you can do the same thing with arrays:
std::array<int, 4> values {{ 8, 5, 2, 9 }};
auto [v0, v1, v2, v3] = values;
std::cout << "Sum: " << (v0 + v1 + v2 + v3) << "\n";
int values2[] = { 5, 4, 3 };
auto [i0, i1, i2] = value2;
std::cout << i0 << "; " << i1 << "; " << i2 << "\n";
With std::pair
:
auto pair = std::make_pair(19451019, "María Zambrana");
auto& [date, name] = pair;
std::cout << date << ": " << name << "\n";
Or even with structs!!!
struct Point
{
int x;
int y;
};
Point p { 10, 25 };
auto [x, y] = p;
std::cout << "(" << x << "; " << y << ")\n";
This feature makes iterating maps incredibly elegant.
Compare this pre-C++11 code:
for (std::map<std::string, std::string>::const_iterator it = translations.begin();
it != translations.end();
it++)
{
std::cout << "English: " << it->first << "; Spanish: " << it->second << "\n";
}
Using C++17, it could have been written like this in C++17:
for (auto& [english, spanish] : translations)
{
std::cout << "English: " << english << "; Spanish: " << spanish << "\n";
}
Amazing!