6 min read

In this article by Jacek Galowicz author of the book C++ STL Cookbook, we will learn new C++ features and how to use structured bindings to return multiple values at once.

(For more resources related to this topic, see here.)

Introduction

C++ got a lot of additions in C++11, C++14, and most recently C++17. By now, it is a completely different language than it was just a decade ago. The C++ standard does not only standardize the language, as it needs to be understood by the compilers, but also the C++ standard template library (STL).

We will see how to access individual members of pairs, tuples, and structures comfortably with structured bindings, and how to limit variable scopes with the new if and switch variable initialization capabilities. The syntactical ambiguities, which were introduced by C++11 with the new bracket initialization syntax, which looks the same for initializer lists, were fixed by new bracket initializer rules. The exact type of template class instances can now be deduced from the actual constructor arguments, and if different specializations of a template class shall result in completely different code, this is now easily expressible with constexpr-if. The handling of variadic parameter packs in template functions became much easier in many cases with the new fold expressions. At last, it became more comfortable to define static globally accessible objects in header-only libraries with the new ability to declare inline variables, which was only possible for functions before.

Using structured bindings to return multiple values at once

C++17 comes with a new feature which combines syntactic sugar and automatic type deduction: Structured bindings. These help assigning values from pairs, tuples, and structs into individual variables.

How to do it…

Applying a structured binding in order to assign multiple variables from one bundled structure is always one step:

  • Accessing std::pair: Imagine we have a mathematical function divide_remainder, which accepts a dividend and a divisor parameter, and returns the fraction of both as well as the remainder. It returns those values using an std::pair bundle:
    std::pair<int, int> divide_remainder(int dividend, int divisor);

    Instead of accessing the individual values of the resulting pair like this:

    const auto result (divide_remainder(16, 3));
    std::cout << "16 / 3 is " << result.first << " with a remainder of " <<
    result.second << "n";

    We can now assign them to individual variables with expressive names, which is much better to read:

    auto [fraction, remainder] = divide_remainder(16, 3);
    std::cout << "16 / 3 is " << fraction << " with a remainder of " <<
    remainder << "n";
  • Structured bindings also work with std::tuple: Let’s take the following example function, which gets us online stock information:
    std::tuple<std::string, std::chrono::time_point, double>
    stock_info(const std::string &name);

    Assigning its result to individual variables looks just like in the example before:

    const auto [name, valid_time, price] = stock_info("INTC");
  • Structured bindings also work with custom structures: Let’s assume a structure like the following:
    struct employee {
    unsigned id;
    std::string name;
    std::string role;
    unsigned salary;
    };

    Now we can access these members using structured bindings. We will even do that in a loop, assuming we have a whole vector of those:

    int main()
    {
    std::vector<employee> employees {/* Initialized from somewhere */};
    for (const auto &[id, name, role, salary] : employees) {
    std::cout << "Name: " << name
    << "Role: " << role
    << "Salary: " << salary << "n";
    }
    }

How it works…

Structured bindings are always applied with the same pattern:

auto [var1, var2, ...] = <pair, tuple, struct, or array expression>;
  • The list of variables var1, var2, ... must exactly match the number of variables which are contained by the expression being assigned from.
  • The <pair, tuple, struct, or array expression> must be one of the following:
    • An std::pair.
    • An std::tuple.
    • A struct. All members must be non-static and be defined in the same base class.
    • An array of fixed size.
  • The type can be auto, const auto, const auto& and even auto&&.

    Not only for the sake of performance, always make sure to minimize needless copies by using references when appropriate.

If we write too many or not enough variables between the square brackets, the compiler will error out, telling us about our mistake:

std::tuple<int, float, long> tup {1, 2.0, 3};
auto [a, b] = tup;

This example obviously tries to stuff a tuple variable with three members into only two variables. The compiler immediately chokes on this and tells us about our mistake:

error: type 'std::tuple<int, float, long>' decomposes into 3 elements, but
only 2 names were provided
auto [a, b] = tup;

There’s more…

A lot of fundamental data structures from the STL are immediately accessible using structured bindings without us having to change anything. Consider for example a loop, which prints all items of an std::map:

std::map<std::string, size_t> animal_population {
{"humans", 7000000000},
{"chickens", 17863376000},
{"camels", 24246291},
{"sheep", 1086881528},
/* … */
};
for (const auto &[species, count] : animal_population) {
std::cout << "There are " << count << " " << species << " on this
planet.n";
}

This particular example works, because when we iterate over a std::map container, we get std::pair<key_type, value_type> items on every iteration step. And exactly those are unpacked using the structured bindings feature (Assuming that the species string is the key, and the population count the value being associated with the key), in order to access them individually in the loop body.

Before C++17, it was possible to achieve a similar effect using std::tie:

int remainder;
std::tie(std::ignore, remainder) = divide_remainder(16, 5);
std::cout << "16 % 5 is " << remainder << "n";

This example shows how to unpack the result pair into two variables. std::tie is less powerful than structured bindings in the sense that we have to define all variables we want to bind to before. On the other hand, this example shows a strength of std::tie which structured bindings do not have: The value std::ignore acts as a dummy variable. The fraction part of the result is assigned to it, which leads to that value being dropped because we do not need it in that example.

Back in the past, the divide_remainder function would have been implemented the following way, using output parameters:

bool divide_remainder(int dividend, int divisor, int &fraction, int
&remainder);

Accessing it would have looked like the following:

int fraction, remainder;
const bool success {divide_remainder(16, 3, fraction, remainder)};
if (success) {
std::cout << "16 / 3 is " << fraction << " with a remainder of " <<
remainder << "n";
}

A lot of people will still prefer this over returning complex structures like pairs, tuples, and structs, arguing that this way the code would be faster, due to avoided intermediate copies of those values. This is not true any longer for modern compilers, which optimize intermediate copies away.

Apart from the missing language features in C, returning complex structures via return value was considered slow for a long time, because the object had to be initialized in the returning function, and then copied into the variable which shall contain the return value on the caller side. Modern compilers support return value optimization (RVO), which enables for omitting intermediate copies.

Summary

Thus we successfully studied how to use structured bindings to return multiple values at once in C++ 17 using code examples.

Resources for Article:


Further resources on this subject:

LEAVE A REPLY

Please enter your comment!
Please enter your name here