Tuple Unpacking (Err, Structured Binding)

Basics

Wanted: Python’s tuple unpacking

a, b, c = [1, 2, 3]   # from list
a, b, c = (1, 2, 3)   # from tuple
...

For example …

struct point
{
    int x, y;
};
point p{1,2};
auto [x, y] = p;

Variations, Compatible Data Types

Variant

Meaning

auto [...]

By copy

auto& [...]

By reference

const auto& [...]

By const reference

Works on structured data

  • struct

  • std::tuple

  • std::pair

Works on arrays too

int array[] {1, 2, 3};
auto [x, y, z] = array;

Not on pointer to array;

int array[] {1, 2, 3};
int *parray = array;
auto [x, y, z] = parray;
error: cannot decompose non-array non-class type ‘int*’
  25 |     const auto& [x, y, z] = array;
     |                 ^~~~~~~~~

struct: By Copy

#include <gtest/gtest.h>

struct point
{
    int x, y;
};

TEST(structured_binding_suite, struct_copy)
{
    point p{1,2};

    auto [x, y] = p;

    ASSERT_EQ(x, p.x);
    ASSERT_EQ(y, p.y);

    // copy
    x = y = 666;
    ASSERT_NE(p.x, 666);
    ASSERT_NE(p.y, 666);
}

struct: Reference

#include <gtest/gtest.h>

struct point
{
    int x, y;
};

TEST(structured_binding_suite, struct_reference)
{
    point p{1,2};

    auto& [x, y] = p;

    ASSERT_EQ(x, p.x);
    ASSERT_EQ(y, p.y);

    // x, y are *references*, so assignment to those assigns to the
    // *original* struct members
    x = y = 666;
    ASSERT_EQ(p.x, 666);
    ASSERT_EQ(p.y, 666);
}

struct: By const Reference

#include <gtest/gtest.h>

struct point
{
    int x, y;
};

TEST(structured_binding_suite, struct_const_reference)
{
    point p{1,2};

    const auto& [x, y] = p;

    ASSERT_EQ(x, p.x);
    ASSERT_EQ(y, p.y);

    // x, y are *references*, so addresses must match
    ASSERT_EQ(&x, &p.x);
    ASSERT_EQ(&y, &p.y);

    // const reference -> does not compile
    // x = y = 666;
}

std::tuple: By Copy

#include <gtest/gtest.h>

TEST(structured_binding_suite, tuple_copy)
{
    auto t = std::make_tuple(1, 2, 3);

    auto [x, y, z] = t;

    ASSERT_EQ(x, std::get<0>(t));
    ASSERT_EQ(y, std::get<1>(t));
    ASSERT_EQ(z, std::get<2>(t));

    // x, y, z are *copies*, so original tuple is *not* affected by
    // assignment
    x = y = z = 666;
    ASSERT_NE(std::get<0>(t), 666);
    ASSERT_NE(std::get<1>(t), 666);
    ASSERT_NE(std::get<2>(t), 666);
}

std::tuple: By Reference

#include <gtest/gtest.h>

TEST(structured_binding_suite, tuple_reference)
{
    auto t = std::make_tuple(1, 2, 3);

    auto& [x, y, z] = t;

    ASSERT_EQ(x, std::get<0>(t));
    ASSERT_EQ(y, std::get<1>(t));
    ASSERT_EQ(z, std::get<2>(t));

    // x, y, z are *reference*, so assignment to those goes into the
    // *original* tuple members
    x = y = z = 666;
    ASSERT_EQ(std::get<0>(t), 666);
    ASSERT_EQ(std::get<1>(t), 666);
    ASSERT_EQ(std::get<2>(t), 666);
}

std::tuple: By const Reference

#include <gtest/gtest.h>

TEST(structured_binding_suite, tuple_const_reference)
{
    auto t = std::make_tuple(1, 2, 3);

    const auto& [x, y, z] = t;

    ASSERT_EQ(x, std::get<0>(t));
    ASSERT_EQ(y, std::get<1>(t));
    ASSERT_EQ(z, std::get<2>(t));

    // x, y, z are *const references*, so compiler doesn't let me
    // assign ("assignment of read-only reference")

    // x = y = z = 666;  <--- does not compile

    // but lets make sure that x, y, z are still *references* -> the
    // addresses must match
    ASSERT_EQ(&x, &std::get<0>(t));
    ASSERT_EQ(&y, &std::get<1>(t));
    ASSERT_EQ(&z, &std::get<2>(t));
}

Arrays: By Copy

#include <gtest/gtest.h>

TEST(structured_binding_suite, array_copy)
{
    int array[] {1, 2, 3};

    auto [x, y, z] = array;

    ASSERT_EQ(x, array[0]);
    ASSERT_EQ(y, array[1]);
    ASSERT_EQ(z, array[2]);

    // x, y, z are *copies*, so assignment does not go through to the
    // originals
    x = y = z = 666;
    ASSERT_NE(array[0], 666);
    ASSERT_NE(array[1], 666);
    ASSERT_NE(array[2], 666);
}

Arrays: By Reference

#include <gtest/gtest.h>

TEST(structured_binding_suite, array_reference)
{
    int array[] {1, 2, 3};

    auto& [x, y, z] = array;

    ASSERT_EQ(x, array[0]);
    ASSERT_EQ(y, array[1]);
    ASSERT_EQ(z, array[2]);

    // x, y, z are *references*, so assignment to those goes though
    // right into the original array members
    x = y = z = 666;
    ASSERT_EQ(array[0], 666);
    ASSERT_EQ(array[1], 666);
    ASSERT_EQ(array[2], 666);
}

Arrays: By const Reference

#include <gtest/gtest.h>

TEST(structured_binding_suite, array_const_reference)
{
    int array[] {1, 2, 3};

    const auto& [x, y, z] = array;

    ASSERT_EQ(x, array[0]);
    ASSERT_EQ(y, array[1]);
    ASSERT_EQ(z, array[2]);

    // x, y, z are *const references*, so the compiler won't let me
    // assign ("error: assignment of read-only variable")

    // x = y = z = 666;  <--- does not compile

    // but lets make sure that x, y, z are still *references* (not
    // that the compiler error message says "assignment of read-only
    // **variable**" which is suspicious). lets make sure that we
    // really talk about references:
    ASSERT_EQ(&x, &array[0]);
    ASSERT_EQ(&y, &array[1]);
    ASSERT_EQ(&z, &array[2]);
}