C++14 | C++17 | |
---|---|---|
tuple<int, string> func();
auto tup = func();
int i = get<0>(tup);
string s = get<1>(tup);
use(s, ++i); |
tuple<int, string> func();
int i;
string s;
std::tie(i,s) = func();
use(s, ++i); |
tuple<int, string> func();
auto [ i, s ] = func();
use(s, ++i); |
C++17 | compiler |
---|---|
pair<int, string> func();
auto [ i, s ] = func();
use(s, ++i); |
pair<int, string> func();
auto __tmp = func();
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] = func();
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 << str; }
};
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 << str; }
};
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;
_tmp3.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 quality), 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 func();
auto [ i, s ] = func();
use(s, ++i); |
struct Foo {
int x;
string str;
};
Foo func();
Foo __tmp = func();
auto & i = __tmp.x;
auto & s = __tmp.str;
use(s, ++i); |
Implement your own get(), tuple_size, tuple_element
For any class/struct that doesn't work by default, you need to implement your own custom get<>()
and you also need to implement tuple_size
and tuple_element
.
C++17 |
---|
class Foo {
// ...
public:
template <int N> auto & get() /*const?*/ { /*...*/ }
};
// or get outside class
template<int N> auto & get(Foo /*const?*/ & foo) { /*...*/ }
//...
// tuple_size/element specialized
// yes, in namespace std
namespace std {
// how many elements does Foo have
template<> struct tuple_size<Foo> { static const int value = 3; }
// what type is element N
template<int N> struct tuple_element<N, Foo> { using type = ...add code here...; }
}
Foo func();
auto [ i, s ] = func();
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)
{
//...
} |