Lucky Resistor
Menu
  • Home
  • Learn
    • Learn C++
    • Product Photography for Electronics
      • Required Equipment and Software
    • Soldering for Show
  • Projects
  • Libraries
  • Applications
  • Shop
  • About
    • About Me
    • Contact
    • Stay Informed
  •  
Menu

Auto and Structured Binding

Posted on 2019-07-162022-09-04 by Lucky Resistor

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

It's Time to Use #pragma once

It’s Time to Use #pragma once

In my opinion, preprocessor macros and the outdated #include mechanism are one of the worst parts of the C++ language. It is not just these things are causing a lot of problems, even more, it ...
Read More
How and Why to use Namespaces

How and Why to use Namespaces

Namespaces are a feature of C++ which address the problem of name conflicts. There is a "global" namespace, where everything lives which was declared without namespace. Especially the Arduino environment declares a huge amount of ...
Read More
Write Less Code using the "auto" Keyword

Write Less Code using the “auto” Keyword

The auto keyword was introduced with C++11. It reduces the amount of code you have to write, reduces repetitive code and the number of required changes. Sadly, many C++ developers are not aware of how ...
Read More
Bit Manipulation using Templates

Bit Manipulation using Templates

Did you read my article about C++ templates for embedded code? You learned how to use function templates. This post adds a practical example to this topic. Bit Manipulation You may be familiar with bit ...
Read More
Units of Measurements for Safe C++ Variables

Units of Measurements for Safe C++ Variables

In your program code, you often deal with unitless values. You can add the units of measurements in the variable or function name, or by adding a comment, but there is still the risk you ...
Read More
Guide to Modular Firmware

Guide to Modular Firmware

This article is for embedded software developers with a solid working knowledge of C or C++, but who struggle with large and complex projects. If you learn to develop embedded code, e.g. using the Arduino ...
Read More

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Stay Updated

Join me on Mastodon!

Top Posts & Pages

  • How and Why to use Namespaces
  • Storage Boxes System for 3D Print
  • Use Enum with More Class!
  • Circle Pattern Generator
  • Real Time Counter and Integer Overflow
  • Circle Pattern Generator
  • Logic Gates Puzzles
  • C++ Templates for Embedded Code
  • C++ Templates for Embedded Code (Part 2)
  • Logic Gates Puzzle 101

Latest Posts

  • The Importance of Wall Profiles in 3D Printing2023-02-12
  • The Hinges and its Secrets for Perfect PETG Print2023-02-07
  • Better Bridging with Slicer Guides2023-02-04
  • Stronger 3D Printed Parts with Vertical Perimeter Linking2023-02-02
  • Logic Gates Puzzle 1012023-02-02
  • Candlelight Emulation – Complexity with Layering2023-02-01
  • Three Ways to Integrate LED Light Into the Modular Lantern2023-01-29
  • The 3D Printed Modular Lantern2023-01-17

Categories

  • 3D Printing
  • Build
  • Common
  • Fail
  • Fun
  • Learn
  • Projects
  • Puzzle
  • Recommendations
  • Request for Comments
  • Review
  • Software
Copyright (c)2022 by Lucky Resistor. All rights reserved.