C++17 Structured Bindings

Introduced under proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0144r0.pdf, Structured Bindings give us the ability to declare multiple variables initialised from a tuple or struct.

Tuples

Pre C++17

When “unpacking” a tuple, for example, you had to first declare the variables, and then use std::tie to get the values back.

Example:

#include <iostream>
#include <tuple>

int main()
{
    auto tuple = std::make_tuple(1, 'a', 2.3);

    // first we have to declare the variables
    int i;
    char c;
    double d;

    // now we can unpack the tuple into its individual components
    std::tie(i, c, d) = tuple;

    std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';
    
    return 0;
}

Build and run:

$ clang++ -std=c++14 main.cpp
$ ./a.out
i=1 c=a d=2.3

C++17

With the introduction of structured bindings, when unpacking our tuple we can declare the variables inline, at the call site, using the following syntax:

auto [ var1, var2 ] = tuple;

Example:

#include <iostream>
#include <tuple>

int main()
{
    auto tuple = std::make_tuple(1, 'a', 2.3);

    // unpack the tuple into individual variables declared at the call site
    auto [ i, c, d ] = tuple;

    std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';

    return 0;
}

Build and run:

$ clang++-4.0 -std=c++1z main.cpp
$ ./a.out
i=1 c=a d=2.3

Note:

I am using the llvm nightly clang++-4.0 snapshot for the C++17 examples.

I added the nightly package sources to apt:

$ grep llvm /etc/apt/sources.list
deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial main
deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial main

Then updated apt and installed

$ sudo apt update
$ sudo apt install clang-4.0

Obtaining references to tuple members

Pre C++17

A major disadvantage to unpacking with std::tie is that it is impossible to obtain a reference to a tuple member. This is because references can’t be reseated. Once a reference is created, it cannot be later made to reference another object.

To illustrate, see here an attempt to create a reference and assign it to a member of the tuple using std::tie:

#include <iostream>
#include <tuple>

int main()
{
    auto tuple = std::make_tuple(1, 'a', 2.3);

    // first we have to declare the variables
    int& i; // syntax error - can't declare a reference without initialising it
    char c; 
    double d;
    std::tie(i, c, d) = tuple;

    std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';

    // change the value of i inside the tuple
    i = 2;

    // show that the value inside the tuple has changed
    std::cout << "tuple<0>=" << std::get<0>(tuple) << '\n';

    return 0;
}

Build:

$ clang++ -std=c++14 main.cpp
main.cpp:8:10: error: declaration of reference variable 'i' requires an initializer
    int& i; // syntax error - can't declare a reference without initialising it
        ^
1 error generated.

The only way in pre C++17 to obtain a reference to a tuple member was to explicitly use std::get<index>(tuple):

#include <iostream>
#include <tuple>

int main()
{
    auto tuple = std::make_tuple(1, 'a', 2.3);

    // unpack the tuple into its individual components
    auto& i = std::get<0>(tuple);
    auto& c = std::get<1>(tuple);
    auto& d = std::get<2>(tuple);

    std::cout << "i=" << i << ", c=" << c << ", d=" << d << '\n';

    // change the value of i inside the tuple
    i = 2;

    // show that the value inside the tuple has changed
    std::cout << "tuple<0>=" << std::get<0>(tuple) << '\n';

    return 0;
}

Build and run:

$ clang++ -std=c++14 main.cpp
$ ./a.out
i=1, c=a, d=2.3
tuple<0>=2

C++17

With the introduction of structured bindings, we can now obtain a reference to the tuple members using auto&:

#include <iostream>
#include <tuple>

int main()
{
    auto tuple = std::make_tuple(1, 'a', 2.3);

    // unpack the tuple into its individual components
    auto& [ i, c, d ] = tuple;

    std::cout << "i=" << i << ", c=" << c << ", d=" << d << '\n';

    // change the value of i inside the tuple
    i = 2;

    // show that the value inside the tuple has changed
    std::cout << "tuple<0>=" << std::get<0>(tuple) << '\n';

    return 0;
}

Build and run:

$ clang++-4.0 -std=c++1z main.cpp
$ ./a.out
i=1, c=a, d=2.3
tuple<0>=2

Structs

Structured bindings also allow us to “unpack” the members of a struct into its individual components.

#include <iostream>

struct Foo
{
    int i;
    char c;
    double d;
};

int main()
{
    Foo f { 1, 'a', 2.3 };

    // unpack f's members into individual variables declared at the call site
    auto [ i, c, d ] = f;

    std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';

    return 0;
}
$ clang++-4.0 -std=c++1z main.cpp
$ ./a.out
i=1 c=a d=2.3

Similarly, we can obtain a reference to the members of a struct, and then change their value using our reference

#include <iostream>

struct Foo
{
    // unpack the struct into individual variables declared at the call site
    int i;
    char c;
    double d;
};

int main()
{
    Foo f { 1, 'a', 2.3 };

    // unpack the struct into individual variables declared at the call site
    auto& [ i, c, d ] = f;

    std::cout << "i=" << i << " c=" << c << " d=" << d << '\n';

    // change the member c
    c = 'b';

    // show the value changed in f
    std::cout << "f.c=" << f.c << '\n';

    return 0;
}
$ clang++-4.0 -std=c++1z main.cpp
$ ./a.out
i=1 c=a d=2.3
f.c=b

We can also capture as a forwarding reference using auto&&.

That is, auto&& will resolve to auto& for lvalue references, and auto&& for rvalud references.

Here is an example of capturing the output from a range-based for loop over a temporary map

#include <iostream>
#include <map>

std::map<std::string, int> get_map()
{
    return {
        { "hello", 1 },
        { "world", 2 },
        { "it's",  3 },
        { "me",    4 },
    };
}

int main()
{
    for (auto&& [ k, v ] : get_map())
        std::cout << "k=" << k << " v=" << v << '\n';

    return 0;
}
$ clang++-4.0 -std=c++1z main.cpp
$ ./a.out
k=hello v=1
k=it's v=3
k=me v=4
k=world v=2
Written on August 19, 2016