This article is just a short follow-up article for “Write Less Code using the ‘auto’ Keyword”. Structured binding is something handy, introduced in C++17. Therefore, only the latest compiler will support it.
If you are mainly write embedded code, you may skip this article, because it will take some years until C++17 support is available for your platform. If you also write desktop code, read on, you may like this feature.
Find a detailed reference about structural binding here. The following sections will explain the feature using several use cases.
Multiple Return Values using Tuples
Let us write a function to convert a string
into an integer. For error handling, we like to use parameters or return values, but no exceptions. You may consider these variants:
uint32_t getInt(const std::string &text, bool *ok); uint32_t getInt(const std::string &text, bool &ok); bool getInt(const std::string &text, uint32_t &result);
The problem is, if you like to use the full range of the uint32_t
, you need a second value to report of the conversion was successful or not. It may be a bool, or even better an enum
to report this result.
#include <iostream> #include <cstdint> #include <string> #include <limits> uint32_t safeMultiplication(uint32_t a, uint32_t b, bool *ok = nullptr) { uint32_t result; if (__builtin_mul_overflow(a, b, &result)) { if (ok != nullptr) { *ok = false; } return 0; } if (ok != nullptr) { *ok = true; } return result; } uint32_t safeAdd(uint32_t a, uint32_t b, bool *ok = nullptr) { uint32_t result; if (__builtin_add_overflow(a, b, &result)) { if (ok != nullptr) { *ok = false; } return 0; } if (ok != nullptr) { *ok = true; } return result; } uint32_t getInt(const std::string &text, bool *ok = nullptr) { uint32_t result = 0; uint32_t factor = 1; bool factorValid = true; for (auto it = text.rbegin(); it != text.rend(); ++it) { if (*it < '0' || *it > '9') { if (ok != nullptr) { *ok = false; } return 0; } auto value = static_cast<uint32_t>(*it - '0'); if (value > 0) { if (!factorValid) { if (ok != nullptr) { *ok = false; } return 0; } bool multiplicationOk; value = safeMultiplication(factor, value, &multiplicationOk); if (!multiplicationOk) { if (ok != nullptr) { *ok = false; } return 0; } bool additionOk; result = safeAdd(result, value, &additionOk); if (!additionOk) { if (ok != nullptr) { *ok = false; } return 0; } } if (factorValid) { bool multiplicationOk; factor = safeMultiplication(factor, 10u, &multiplicationOk); if (!multiplicationOk) { factorValid = false; } } } if (ok != nullptr) { *ok = true; } return result; } void testText(const std::string &text) { bool ok; const auto number = getInt(text, &ok); std::cout << text << " -> " << number << " ok: " << (ok ? "yes" : "no") << std::endl; } int main(int argc, const char * argv[]) { testText("text"); testText("4294967295"); testText("4294967296"); testText("00004294967295"); testText("x123"); return 0; }
The example code works well. For simplicity, it uses two built-in functions __builtin_add_overflow
and __builtin_mul_overflow
from the GCC and clang compiler.
Using two return values would simplify the interface. It can be done using std::tuple
and structured binding.
#include <iostream> #include <cstdint> #include <string> #include <limits> #include <tuple> using Result = std::tuple<uint32_t, bool>; Result safeMultiplication(uint32_t a, uint32_t b) { uint32_t result; if (__builtin_mul_overflow(a, b, &result)) { return std::make_tuple(0, false); } return std::make_tuple(result, true); } Result safeAdd(uint32_t a, uint32_t b) { uint32_t result; if (__builtin_add_overflow(a, b, &result)) { return std::make_tuple(0, false); } return std::make_tuple(result, true); } Result getInt(const std::string &text) { uint32_t result = 0; uint32_t factor = 1; bool factorValid = true; for (auto it = text.rbegin(); it != text.rend(); ++it) { if (*it < '0' || *it > '9') { return std::make_tuple(0, false); } const auto digitValue = static_cast<uint32_t>(*it - '0'); if (digitValue > 0) { if (!factorValid) { return std::make_tuple(0, false); } const auto[value, multiplicationOk] = safeMultiplication(factor, digitValue); if (!multiplicationOk) { return std::make_tuple(0, false); } bool additionOk; std::tie(result, additionOk) = safeAdd(result, value); if (!additionOk) { return std::make_tuple(0, false); } } if (factorValid) { bool multiplicationOk; std::tie(factor, multiplicationOk) = safeMultiplication(factor, 10u); if (!multiplicationOk) { factorValid = false; } } } return std::make_tuple(result, true); } void testText(const std::string &text) { const auto[number, ok] = getInt(text); std::cout << text << " -> " << number << " ok: " << (ok ? "yes" : "no") << std::endl; } int main(int argc, const char * argv[]) { testText("text"); testText("4294967295"); testText("4294967296"); testText("00004294967295"); testText("x123"); return 0; }
It simplified the code with just a single downside. While the initial implementation made the ok
optional, the new version will get it for each result.
Bind Elements of a Struct
You can bind to members of any struct using the same syntax:
struct Result { uint32_t value; bool ok; } Result result = {10, true} int main() { auto[value, ok] = result; }
If we use a struct
in our example, it simplifies several lines, but requires some additions elsewhere.
#include <iostream> #include <cstdint> #include <string> #include <limits> #include <tuple> struct Result { uint32_t value; bool ok; constexpr static Result error() { return {0, false}; } constexpr static Result success(uint32_t value) { return {value, true}; } }; Result safeMultiplication(uint32_t a, uint32_t b) { uint32_t result; if (__builtin_mul_overflow(a, b, &result)) { return Result::error(); } return Result::success(result); } Result safeAdd(uint32_t a, uint32_t b) { uint32_t result; if (__builtin_add_overflow(a, b, &result)) { return Result::error(); } return Result::success(result); } Result getInt(const std::string &text) { uint32_t result = 0; uint32_t factor = 1; bool factorValid = true; for (auto it = text.rbegin(); it != text.rend(); ++it) { if (*it < '0' || *it > '9') { return Result::error(); } const auto digitValue = static_cast<uint32_t>(*it - '0'); if (digitValue > 0) { if (!factorValid) { return Result::error(); } const auto[value, multiplicationOk] = safeMultiplication(factor, digitValue); if (!multiplicationOk) { return Result::error(); } const auto[immediateResult, additionOk] = safeAdd(result, value); if (!additionOk) { return Result::error(); } result = immediateResult; } if (factorValid) { const auto[newFactor, multiplicationOk] = safeMultiplication(factor, 10u); if (!multiplicationOk) { factorValid = false; } factor = newFactor; } } return Result::success(result); } void testText(const std::string &text) { const auto[number, ok] = getInt(text); std::cout << text << " -> " << number << " ok: " << (ok ? "yes" : "no") << std::endl; } int main(int argc, const char * argv[]) { testText("text"); testText("4294967295"); testText("4294967296"); testText("00004294967295"); testText("x123"); return 0; }
The struct made the status optional, because we can write a line like this:
const auto value = getInt(text).value;
Or only check if the value is valid:
if (getInt(text).ok) { // ... }
Binding to an Array
Binding values from an array is another interesting use:
#include <iostream> int table[4][4] = { { 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}, }; int main(int argc, const char * argv[]) { for (const auto &row : table) { const auto [a, b, c, d] = row; std::cout << a << ", " << b << ", " << c << ", " << d << std::endl; } return 0; }
References
I found structural binding also useful creating references to values for actual modification, or const
references to avoid copy operations.
#include <iostream> int table[4][4] = { { 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}, }; int main(int argc, const char * argv[]) { for (auto &row : table) { auto& [a, b, c, d] = row; std::swap(a, d); std::swap(b, c); } for (const auto &row : table) { const auto& [a, b, c, d] = row; std::cout << a << ", " << b << ", " << c << ", " << d << std::endl; } return 0; }
Conclusion
If you already use a C++17 capable compiler, like the GCC, clang or Visual Studio, try to use structural binding in your project. You can find a table with the current compiler support in this document.
- It can create cleaner interfaces, where all values are returned with the
return
statement. - Returned objects can be directly split into variables.
- Working with
std::tuple
values is straightforward. - Array tables can be accessed in a column like way.
If you have questions, miss some information or have any feedback, feel free to add a comment below.
Learn More

Extreme Integers – Doom from Below

Guide to Modular Firmware

Consistent Error Handling

Use Enum with More Class!

C++ Templates for Embedded Code (Part 2)
