Skip to content

Latest commit

 

History

History
316 lines (257 loc) · 4.81 KB

File metadata and controls

316 lines (257 loc) · 4.81 KB

Structured Bindings

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)
{
//...
}