| C++14 | C++14 | C++17 |
|---|---|---|
tuple<int, string> stuff();
auto tup = stuff();
int i = get<0>(tup);
string s = get<1>(tup);
use(s, ++i); |
tuple<int, string> stuff();
int i;
string s;
std::tie(i,s) = stuff();
use(s, ++i); |
tuple<int, string> stuff();
auto [ i, s ] = stuff();
use(s, ++i); |
| C++17 | compiler |
|---|---|
pair<int, string> stuff();
auto [ i, s ] = stuff();
use(s, ++i); |
pair<int, string> stuff();
auto __tmp = stuff();
auto & i = get<0>(__tmp);
auto & s = get<1>(__tmp);
use(s, ++i); |
Note, in the above, __tmp is a copy, but i and s are references. Or I should say "references" in quotes. Not exactly references, but real compiler synonyms for the members. (They are not real references as things like decltype "look through" the references to the actual members.)
So even though auto [i,s] = stuff(); has no & anywhere, there are still references involved. For example:
| C++17 | compiler |
|---|---|
#include <string>
#include <iostream>
struct Foo
{
int x = 0;
std::string str = "world";
~Foo() { std::cout << s; }
};
int main()
{
auto [ i, s ] = Foo();
std::cout << "hello ";
s = "structured bindings";
} |
#include <string>
#include <iostream>
struct Foo
{
int x = 0;
std::string str = "world";
~Foo() { std::cout << s; }
};
int main()
{
auto __tmp = Foo();
std::cout << "hello ";
__tmp.str = "structured bindings";
} |
| Output | |
| hello structured bindings | |
Note that the s = "structured bindings"; is modifying Foo::str inside of the temporary (hidden) Foo, so that when the temporary Foo is destroyed, its destructor prints structured bindings instead of world.
So what does a & do in a structured binding declaration?
It gets applied to the hidden __tmp variable:
| C++17 | compiler |
|---|---|
struct X { int i = 0; };
X makeX();
X x;
auto [ b ] = makeX();
b++;
auto const [ c ] = makeX();
c++;
auto & [ d ] = makeX();
d++;
auto & [ e ] = x;
e++;
auto const & [ f ] = makeX();
f++; |
struct X { int i = 0; };
X makeX();
X x;
auto __tmp1 = makeX();
__tmp1.i++;
auto const __tmp2 = makeX();
__tmp2.i++; //error: can't modify const
auto & __tmp3 = makeX(); //error: non-const ref cannot bind to temp
auto & _tmp3 = x;
x.i++;
auto const & _tmp4 = makeX();
__tmp4.i++; //error: can't modify const |
Wait, pair and tuple are not magic (just nearly impossible to write to STL level), can my types work with this?
YES. The compiler uses get<N>() if available, or can work with plain structs directly:
Structs
| C++17 | compiler |
|---|---|
struct Foo {
int x;
string str;
};
Foo stuff();
auto [ i, s ] = stuff();
use(s, ++i); |
struct Foo {
int x;
string str;
};
Foo stuff();
Foo __tmp = stuff();
auto & i = __tmp.x;
auto & s = __tmp.str;
use(s, ++i); |
Implement your own get()
| C++17 |
|---|
class Foo {
// ...
public:
template <int N> auto get() /*const?*/ { /*...*/ }
};
namespace std {
template ... tuple_size ...
template ... tuple_element ...
}
// or get outside class
template<int N> auto get(Foo /*const?*/ & foo) { /*...*/ }
//...
Foo stuff();
auto [ i, s ] = stuff();
use(s, ++i); |
Arrays, std::array, etc, oh my!
| etc |
|---|
int arr[4] = { /.../ };
auto [ a, b, c, d ] = arr;
auto [ t, u, v ] = std::array<int,3>();
// now we're talkin'
for (auto && [key, value] : my_map)
{
//...
} |