Book Image

Modern C++ Programming Cookbook

By : Marius Bancila
Book Image

Modern C++ Programming Cookbook

By: Marius Bancila

Overview of this book

C++ is one of the most widely used programming languages. Fast, efficient, and flexible, it is used to solve many problems. The latest versions of C++ have seen programmers change the way they code, giving up on the old-fashioned C-style programming and adopting modern C++ instead. Beginning with the modern language features, each recipe addresses a specific problem, with a discussion that explains the solution and offers insight into how it works. You will learn major concepts about the core programming language as well as common tasks faced while building a wide variety of software. You will learn about concepts such as concurrency, performance, meta-programming, lambda expressions, regular expressions, testing, and many more in the form of recipes. These recipes will ensure you can make your applications robust and fast. By the end of the book, you will understand the newer aspects of C++11/14/17 and will be able to overcome tasks that are time-consuming or would break your stride while developing.
Table of Contents (19 chapters)
Title Page
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

Using structured bindings to handle multi-return values


Returning multiple values from a function is something very common, yet there is no first-class solution in C++ to enable it directly. Developers have to choose between returning multiple values through reference parameters to a function, defining a structure to contain the multiple values or returning a std::pair or std::tuple. The first two use named variables that have the advantage that they clearly indicate the meaning of the return value, but have the disadvantage that they have to be explicitly defined. std::pair has its members called first and second, and std::tuple has unnamed members that can only be retrieved with a function call, but can be copied to named variables using std::tie(). None of these solutions is ideal.

C++17 extends the semantic use of std::tie() into a first-class core language feature that enables unpacking the values of a tuple to named variables. This feature is called structured bindings.

Getting ready

For this recipe, you should be familiar with the standard utility types std::pair and std::tuple and the utility function std::tie().

How to do it...

To return multiple values from a function using a compiler that supports C++17 you should do the following:

  1. Use an std::tuple for the return type.
        std::tuple<int, std::string, double> find() 
        { 
          return std::make_tuple(1, "marius", 1234.5); 
        }
  1. Use structured bindings to unpack the values of the tuple into named objects.
        auto [id, name, score] = find();

 

  1. Use decomposition declaration to bind the returned values to variables inside an if statement or switch statement.
        if (auto [id, name, score] = find(); score > 1000) 
        { 
          std::cout << name << std::endl; 
        }

How it works...

Structured bindings are a language feature that works just like std::tie(), except that we don't have to define named variables for each value that needs to be unpacked explicitly with std::tie(). With structured bindings, we define all named variables in a single definition using the auto specifier so that the compiler can infer the correct type for each variable.

To exemplify this, let's consider the case of inserting items in a std::map. The insert method returns a std::pair containing an iterator to the inserted element or the element that prevented the insertion, and a boolean indicating whether the insertion was successful or not. The following code is very explicit and the use of second or first->second makes the code harder to read because you need to constantly figure out what they represent:

    std::map<int, std::string> m; 

    auto result = m.insert({ 1, "one" }); 
    std::cout << "inserted = " << result.second << std::endl 
              << "value = " << result.first->second << std::endl;

The preceding code can be made more readable with the use of std::tie, that unpacks tuples into individual objects (and works with std::pair because std::tuple has a converting assignment from std::pair):

    std::map<int, std::string> m; 
    std::map<int, std::string>::iterator it; 
    bool inserted; 

    std::tie(it, inserted) = m.insert({ 1, "one" }); 
    std::cout << "inserted = " << inserted << std::endl 
              << "value = " << it->second << std::endl; 

    std::tie(it, inserted) = m.insert({ 1, "two" }); 
    std::cout << "inserted = " << inserted << std::endl 
              << "value = " << it->second << std::endl;

The code is not necessarily simpler because it requires defining in advance the objects that the pair is unpacked to. Similarly, the more elements the tuple has the more objects you need to define, but using named objects makes the code easier to read.

C++17 structured bindings elevate the unpacking of tuple elements into named objects to the rank of a language feature; it does not require the use of std::tie(), and objects are initialized when declared:

    std::map<int, std::string> m; 
    { 
      auto[it, inserted] = m.insert({ 1, "one" }); 
      std::cout << "inserted = " << inserted << std::endl 
                << "value = " << it->second << std::endl; 
    } 

    { 
      auto[it, inserted] = m.insert({ 1, "two" }); 
      std::cout << "inserted = " << inserted << std::endl 
                << "value = " << it->second << std::endl; 
    }

The use of multiple blocks in the above example is necessary because variables cannot be redeclared in the same block, and structured bindings imply a declaration using the auto specifier. Therefore, if you need to make multiple calls like in the example above and use structured bindings you must either use different variable names or multiple blocks as shown above. An alternative to that is to avoid structured bindings and use std::tie(), because it can be called multiple times with the same variables, therefore you only need to declare them once.

In C++17, it is also possible to declare variables in if and switch statements with the form if(init; condition) and switch(init; condition). This could be combined with structured bindings to produce simpler code. In the following example, we attempt to insert a new value into a map. The result of the call is unpacked into two variables, it and inserted, defined in the scope of the if statement in the initialization part. The condition of the if statement is evaluated from the value of the inserted object:

    if(auto [it, inserted] = m.insert({ 1, "two" }); inserted)
    { std::cout << it->second << std::endl; }