C++17: Structured bindings

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!

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s