Imagine we have an object that contains a collection of other objects, such as an instance of our Vector3
struct from previous lessons:
struct Vector3 {
float x;
float y;
float z;
};
int main() {
Vector3 SomeVector{1.0, 2.0, 3.0};
};
Often, we'll want to extract all of its members to standalone variables for further operations:
struct Vector3 {
float x;
float y;
float z;
}
int main() {
Vector3 SomeVector{1.0, 2.0, 3.0};
float a { SomeVector.x };
float b { SomeVector.y };
float c { SomeVector.z };
};
C++ offers a convenient shortcut to this, called structured binding.
Those with experience in other programming languages may already be familiar with this concept. Other common names for it include destructuring and unpacking.
The syntax to use structured binding looks like this:
#include <iostream>
using namespace std;
struct Vector3 {
float x;
float y;
float z;
};
int main(){
Vector3 SomeVector{1.0, 2.0, 3.0};
auto [a, b, c]{SomeVector};
cout << "a = " << a
<< ", b = " << b
<< ", c = " << c;
}
a = 1, b = 2, c = 3
To break this down, first, we use the auto
keyword, which asks the compiler to automatically determine what type each of our new variables will have.
Note we must use auto
here, even if all our variables will have the same type.
After auto
, we open up a set of square brackets - [
and ]
. Within these brackets, we specify the name we want to use for each variable we create.
Finally, we provide the object we want to use to initialize these variables. We can use any initialization syntax here - the following are all options:
auto [a, b, c]{SomeVector};
auto [d, e, f](SomeVector);
auto [g, h, i] = SomeVector;
Note that the names of the variables we’re creating with our structured binding expression need not match the names of those variables within the original object.
The variables we create are mapped to the variables within the source object by position, not by name.
The following code will set a
, b
, and c
to have the types and values of the first, second, and third members of SourceObject
, respectively:
auto [a, b, c]{SourceObject};
It doesn’t matter what those variables are called within the struct or class definition.
In the following program, how can we access the Name
, Level
, and Health
values of MyCharacter
using structured binding?
#include <iostream>
using namespace std;
class Character {
public:
string Name{"Goblin Warrior"};
int Level{5};
int Health{100};
};
int main(){
Character MyCharacter;
// Access MyCharacter variables
}
Much like aggregate initialization, structured binding is mostly useful for simple data types, such as our Vector3
struct.
Once our type gets more complicated, structured binding has some limitations. Firstly, we need to unpack every variable from our object, even if we only need some of them.
An additional effect of this limitation is that, if our type has private data members, we can’t access all of them, so we can’t use structured binding at all.
In those cases, we need to fall back to traditional approaches, such as accessing variables one-by-one, or providing a member function to support the desired access pattern.
Whilst the standard, out-of-the-box implementation of structured binding is basic and limited, one of the main benefits of its existence is the ability to overload it.
This allows us to define exactly how structured binding works with our custom types, allowing us to bypass these limitations, and giving us much more flexibility in general.
However, implementing this requires a deeper understanding of C++ than what is covered in a beginner's course.
We delve into these advanced techniques in the next course
In this lesson, we explored the concept of structured binding in C++. Here are the key takeaways:
auto
keyword is essential in structured binding, allowing the compiler to infer the types of the unpacked variables.This lesson introduces Structured Binding, a handy tool for quickly unpacking simple data structures
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way