Structured Bindings in C++
Introduced in C++17, structured bindings (aka tuple unpacking) are a convenient way to use tuple-like data structures. The idea is to avoid writing multiple assignment statements, and instead, to access individual values in a single statement.
std::map or std::vector.So, why care about structured bindings? Here are some reasons:
- Simplify Code: you can reduce the amount of boilerplate code and focus on the logic that matters.
- Improve Readability: by assigning values in a concise manner, code becomes easier to read and understand.
- Reduce Error Prone Code: minimize errors by avoiding multiple assignment statements.
Syntax
The syntax for structured bindings is as follows:
auto&& [var1, var2, ..., varN] = expression;
where,
autois used to deduce the types of the variables,[var1, var2, ..., varN]is a list of variable names,expressionis the tuple-like structure from which values are extracted.
Behind the scenes, the compiler may generate something like the following pseudo code:
auto&& tmp = expression; // compiler generated
decltype(auto) var1 = tmp.get<0>();
decltype(auto) var2 = tmp.get<1>();
decltype(auto) varN = tmp.get<N>();The compiler generates a unique name for the tmp object. The decltype(auto) preserves the return type of get<>() method (see below). On the other hand, the auto would decay a reference to a copy.
Example: std::map
The scores object is a list of key-value (name-score) pairs. We iterate over the list using two different range-based for loop; with C++14 (item : scores) and with C++17 structured bindings ([name, score] : scores). Obviously, using the structured bindings is more expressive. Using the C++14 ranged-loop, the map iterator returns the <first, second> pair that is less expressive and more error prone.
std::map<std::string, int> scores = {{"Ali", 80}, {"Veli", 90}, {"Deli", 70}};
// Iterate using ranged-loop C++14
for (const auto& item : scores) {
std::cout << "Name: " << item.first << ", Score: " << item.second << '\n';
}
// Iterate using structured bindings C++17
for (auto&& [name, score] : scores) {
std::cout << "Name: " << name << ", Score: " << score << '\n';
}Avoiding copy
Consider the std::map example where the C++14 loop uses reference qualifier on auto but item.first results in a copy. On the other hand, using auto&& results in const std::string& name, which avoids unnecessary copy.
Custom class
#include "Part.h"
#include <iostream>
#include <vector>
int main() {
std::vector<Part> parts;
// input some parts
parts.emplace_back("Bumper", "Body");
parts.emplace_back("Crankshaft", "Engine");
parts.emplace_back("Battery", "Electrical");
parts.emplace_back("Distribution", "Electrical");
// fix the last part
auto& [name, type, id] = parts.back();
name = "Spark Plug";
type = "Ignition";
// print the parts
for (auto&& part : parts) { std::cout << part << std::endl; }
return 0;
}PartStore.cc
#pragma once
#include <iostream>
#include <string>
#include <utility>
/**
* @class Part
* @brief Represents a part with a name, type, and unique ID.
*/
class Part {
public:
Part(std::string name, std::string type):
name_(std::move(name)), type_(std::move(type)), id_(counter++) { }
auto id() const -> uint64_t { return id_; }
auto name() const -> const std::string& { return name_; }
auto type() const -> const std::string& { return type_; }
template<std::size_t Index>
auto& get() {
static_assert(Index < 3, "Index out of bounds");
if constexpr (Index == 0) { return name_; }
if constexpr (Index == 1) { return type_; }
if constexpr (Index == 2) { return id_; }
}
template<std::size_t Index>
auto& get() const {
static_assert(Index < 3, "Index out of bounds");
if constexpr (Index == 0) { return name_; }
if constexpr (Index == 1) { return type_; }
if constexpr (Index == 2) { return id_; }
}
private:
// Overloads the << operator to allow printing a Part object.
friend std::ostream& operator<<(std::ostream& out, const Part& part) {
out << '#' << part.id_ << ": " << part.type_ << " - " << part.name_;
return out;
}
std::string name_; // The name of the part.
std::string type_; // The type of the part.
uint64_t id_; // The unique ID of the part.
// The counter for generating unique IDs.
static inline uint64_t counter = 0;
};
template<>
struct std::tuple_size<Part> {
// The number of elements in Part.
static constexpr const std::size_t value = 3;
};
template<std::size_t Index>
struct std::tuple_element<Index, Part> {
using type = std::conditional_t<Index == 2, uint64_t, std::string>;
};
Part.h