C++ Structured Binding - From Zero to Hero
Tags: cpp, cpp17
Published: 2021-12-01 14:00:37

---
title: C++ Structured Binding - From Zero to Hero
published: true
description: 
tags: cpp, cpp17
canonical_url: https://www.aloneguid.uk/posts/2021/12/cpp-structured-binding/
//cover_image: https://direct_url_to_image.jpg
---

Structured binding allows to initialise multiple entities by members of another object, for instance:

```c++
struct stb_node
{
	int index = 0;
	string value;
};

stb_node node1{1, "First"};
cout << node1.index << ", " << node1.value << endl;

auto [u, v] = node1;
cout << u << ", " << v << endl;
```

Produces output:

```
1, First
1, First
```

When you break in a debugger, it shows that there are two local variables `int u` and `string v` created. `u` and `v` are called **structural bindings**. The purpose of structural bindings is *to make code more readable by binding the value directly to names*. 

This becomes more apparent when working with maps:

```c++
map<int, string> m = ...;

// pre - C++17 way
for (const auto& el : m)
{
	cout << el.first << ", " << el.second << endl;
}

// C++17 way
for (const auto& [index, value] : m)
{
	cout << index << ", " << value << endl;
}
```

In the second syntax option, the code is carrying the context that it's working with `index` and `value`, unlike `el.first` and `el.second` which doesn't make sense. Of course you can create references to key and value i.e. `auto& index = el.first` (and that's what I often do to improve readability) but that makes code more verbose.

Note that `decltype(index)` is `int` and `decltype(value)` is `string` - there is no special magic types involved, these are just other names for struct members.

Also, as with normal assignment, `auto [u, v] = node1` creates a **copy** of struct members, with all the consequences i.e. modifying `u` modifies a copy of `u`.

## Qualifiers

To avoid copying and enforce constant expressions, you can use qualifiers, for instance:

```c++
const auto [u1, v1] = node1;
u1 = 1;
```

fails on line 2 when trying to modify a copy of `node1.index`.

```c++
const auto& [u2, v2] = node1;
u2 = 2;
```

fails on line 2 when trying to modify a **reference** of `node1.index`.

## More Usages

In general, structured bindings can be used with:

0. Structs with public data members as above.
1. Raw C-style arrays.
2. "Tuple-like" objects.

For structs or classes, the decomposed members must be members of the same class definition. Inheritance is not supported. So this won't work:

```c++
struct B {
	int a = 1;
	int b = 2;
};

struct C : B {
    int c = 3;
}

auto [i, j, k] = C{};	// won't work, gives an error "type "C" has no components to bind to"
```

For arrays, binding only works for arrays of known size:

```c++
int arr[] = { 1, 2 };
auto [e1, e2] = arr;	// OK
auto [e1] = arr;	    // won't compile, gives an error "there are more elements than there are binding names"
```

`std::tuple` decomposes nicely i.e.

```c++
std::tuple<char,float,std::string> do_something();
// ...
auto [a,b,c] = do_something();
```

`std::pair` is just like before, and is logically a subset of tuple.

## Redefining Values

Looking at the example before, how do you call `do_someting` again and assign to `a, b, c`? You can't. However, if the types match, use  `std::tie`:

```c++
std::tuple<char, float, std::string> do_something();
// ...
auto [a, b, c] = do_something();
// redefine:
std::tie(a, b, c) = do_something();
```

Of course you could just introduce new set of variables, however you wouldn't be able to do that in a loop, like:

```c++
std::boyer_moore_searcher bm{sub.begin(), sub.end()};
for (auto [beg, end] = bm(text.begin(), text.end()); beg != text.end(); std::tie(beg,end) = bm(end, text.end())) {
	// ...
}
```