Skip to content

Latest commit

 

History

History
332 lines (259 loc) · 5.26 KB

structured_bindings.md

File metadata and controls

332 lines (259 loc) · 5.26 KB

Structured Bindings

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