Structured binding allows to initialise multiple entities by members of another object, for instance:
struct stb_node
{
int index = 0;
string value;
};
stb_node node1{1, "First"};
cout << node1.index << ", " << node1.value << endl;
auto [u, v] = node1;
cout << u << ", " << v << endl;
Produces output:
1, First
1, First
When you break in a debugger, it shows that there are two local variables int u
and string v
created. u
and v
are called structural bindings. The purpose of structural bindings is to make code more readable by binding the value directly to names.
This becomes more apparent when working with maps:
map<int, string> m = ...;
// pre - C++17 way
for (const auto& el : m)
{
cout << el.first << ", " << el.second << endl;
}
// C++17 way
for (const auto& [index, value] : m)
{
cout << index << ", " << value << endl;
}
In the second syntax option, the code is carrying the context that it's working with index
and value
, unlike el.first
and el.second
which doesn't make sense. Of course you can create references to key and value i.e. auto& index = el.first
(and that's what I often do to improve readability) but that makes code more verbose.
Note that decltype(index)
is int
and decltype(value)
is string
- there is no special magic types involved, these are just other names for struct members.
Also, as with normal assignment, auto [u, v] = node1
creates a copy of struct members, with all the consequences i.e. modifying u
modifies a copy of u
.
Qualifiers
To avoid copying and enforce constant expressions, you can use qualifiers, for instance:
const auto [u1, v1] = node1;
u1 = 1;
fails on line 2 when trying to modify a copy of node1.index
.
const auto& [u2, v2] = node1;
u2 = 2;
fails on line 2 when trying to modify a reference of node1.index
.
More Usages
In general, structured bindings can be used with:
- Structs with public data members as above.
- Raw C-style arrays.
- "Tuple-like" objects.
For structs or classes, the decomposed members must be members of the same class definition. Inheritance is not supported. So this won't work:
struct B {
int a = 1;
int b = 2;
};
struct C : B {
int c = 3;
}
auto [i, j, k] = C{}; // won't work, gives an error "type "C" has no components to bind to"
For arrays, binding only works for arrays of known size:
int arr[] = { 1, 2 };
auto [e1, e2] = arr; // OK
auto [e1] = arr; // won't compile, gives an error "there are more elements than there are binding names"
std::tuple
decomposes nicely i.e.
std::tuple<char,float,std::string> do_something();
// ...
auto [a,b,c] = do_something();
std::pair
is just like before, and is logically a subset of tuple.
Redefining Values
Looking at the example before, how do you call do_someting
again and assign to a, b, c
? You can't. However, if the types match, use std::tie
:
std::tuple<char, float, std::string> do_something();
// ...
auto [a, b, c] = do_something();
// redefine:
std::tie(a, b, c) = do_something();
Of course you could just introduce new set of variables, however you wouldn't be able to do that in a loop, like:
std::boyer_moore_searcher bm{sub.begin(), sub.end()};
for (auto [beg, end] = bm(text.begin(), text.end()); beg != text.end(); std::tie(beg,end) = bm(end, text.end())) {
// ...
}
Top comments (0)