1. General Language Features

In this section of the book, we’ll look at widespread improvements to the language that have the potential to make your code more compact and expressive. A perfect example of such a general feature is structured binding. Using that feature, you can leverage a comfortable syntax for tuples (and tuple-like expressions). Something easy in other languages like Python is now possible with C++17.

In this chapter, you’ll learn:

  • Structured bindings/Decomposition declarations
  • How to provide Structured Binding interface for your custom classes
  • Init-statement for if/switch
  • Inline variables and their impact on header-only libraries
  • Lambda expressions that might be used in a constexpr context
  • How to properly wrap the this pointer in lambda expressions
  • Simplified use of nested namespaces
  • How to test for header existence with __has_include directive

Structured Binding Declarations

Do you often work with tuples or pairs?

If not, then you should probably start looking into those handy types. Tuples enable you to bundle data ad-hoc with excellent library support instead of creating small custom types. The language features like structured binding make the code even more expressive and concise.

Consider a function that returns two results in a pair:

std::pair<int, bool> InsertElement(int el) { ... }

You can write:

auto ret = InsertElement(...)

And then refer to ret.first or ret.second. However, referring to values as .first or .second is also not expressive - you can easily confuse the names, and it’s hard to read. Alternatively you can leverage std::tie which will unpack the tuple/pair into local variables:

int index { 0 };
bool flag { false };
std::tie(index, flag) = InsertElement(10);

Such code might be useful when you work with std::set::insert which returns std::pair:

std::set<int> mySet;
std::set<int>::iterator iter;
bool inserted { false };
 
std::tie(iter, inserted) = mySet.insert(10);

if (inserted)
    std::cout << "Value was inserted\n";

As you see, such a simple pattern - returning several values from a function - requires several lines of code. Fortunately, C++17 makes it much simpler!

With C++17 you can write:

std::set<int> mySet;

auto [iter, inserted] = mySet.insert(10);

Now, instead of pair.first and pair.second, you can use variables with concrete names. In addition, you have one line instead of three, and the code is easier to read. The code is also safer as iter and inserted are initialised in the expression.

Such syntax is called a structured binding expression.

The Syntax

The basic syntax for structured bindings is as follows:

auto [a, b, c, ...] = expression;
auto [a, b, c, ...] { expression };
auto [a, b, c, ...] ( expression );

The compiler introduces all identifiers from the a, b, c, ... list as names in the surrounding scope and binds them to sub-objects or elements of the object denoted by expression.

Behind the scenes, the compiler might generate the following pseudo code:

auto tempTuple = expression;
using a = tempTuple.first;
using b = tempTuple.second;
using c = tempTuple.third;

Conceptually, the expression is copied into a tuple-like object (tempTuple) with member variables that are exposed through a, b and c. However, the variables a, b and c are not references; they are aliases (or bindings) to the generated object member variables. The temporary object has a unique name assigned by the compiler.

For example:

std::pair a(0, 1.0f);
auto [x, y] = a;

x binds to int stored in the generated object that is a copy of a. And similarly, y binds to float.

Modifiers

Several modifiers can be used with structured bindings:

const modifiers:

const auto [a, b, c, ...] = expression;

References:

auto& [a, b, c, ...] = expression;
auto&& [a, b, c, ...] = expression;

For example:

std::pair a(0, 1.0f);
auto& [x, y] = a;
x = 10;  // write access
// a.first is now 10

In the example, x binds to the element in the generated object, that is a reference to a.

Now it’s also quite easy to get a reference to a tuple member:

auto& [ refA, refB, refC, refD ] = myTuple;

You can also add [[attribute]] to structured bindings:

[[maybe_unused]] auto& [a, b, c, ...] = expression;
Structured Binding Limitations

There are several limitations related to structured bindings. They cannot be declared as static or constexpr and also they cannot be used in lambda captures. For example:

constexpr auto [x, y] = std::pair(0, 0);
// generates:
error: structured binding declaration cannot be 'constexpr'

It was also unclear about the linkage of the bindings. Compilers had a free choice to implement it (and thus some of them might allow capturing a structured binding in lambdas). Fortunately, those limitations might be removed due to C++20 proposal (already accepted): P1091: Extending structured bindings to be more like variable declarations.

Binding

Structured Binding is not only limited to tuples, we have three cases from which we can bind from:

1. If the initializer is an array:

// works with arrays:
double myArray[3] = { 1.0, 2.0, 3.0 };  
auto [a, b, c] = myArray;

In this case, an array is copied into a temporary object, and a, b and c refers to copied elements from the array.

The number of identifiers must match the number of elements in the array.

2. If the initializer supports std::tuple_size<>, provides get<N>() and also exposes std::tuple_element functions:

std::pair myPair(0, 1.0f);
auto [a, b] = myPair; // binds myPair.first/second

In the above snippet, we bind to myPair. But this also means that you can provide support for your classes, assuming you add get<N> interface implementation. See an example in the later section.

3. If the initialiser’s type contains only non-static data members:

struct Point  { 
    double x; 
    double y; 
};

Point GetStartPoint() {
    return { 0.0, 0.0 };
}
    
const auto [x, y] = GetStartPoint();

x and y refer to Point::x and Point::y from the Point structure.

The class doesn’t have to be POD, but the number of identifiers must equal to the number of non-static data members. The members must also be accessible from the given context.

Examples

This section will show you a few examples where structured bindings are helpful. In the first one, we’ll use them to write more expressive code, and in the next one, you’ll see how to provide API for your class to support structured bindings.

Expressive Code With Structured Bindings

If you have a map of elements, you might know that internally they are stored as pairs of <const Key, ValueType>.

Now, when you iterate through elements of that map:

for (const auto& elem : myMap) { ... }

You need to write elem.first and elem.second to refer to the key and value. One of the coolest use cases of structured binding is that we can use it inside a range based for loop:

std::map<KeyType, ValueType> myMap;    
// C++14:
for (const auto& elem : myMap) {  
    // elem.first - is velu key
    // elem.second - is the value
} 
// C++17:
for (const auto& [key,val] : myMap) {  
    // use key/value directly
} 

In the above example, we bind to a pair of [key, val] so we can use those names in the loop. Before C++17 you had to operate on an iterator from the map - which returns a pair <first, second>. Using the real names key/value is more expressive.

The above technique can be used in:

Chapter General Language Features/city_map_iterate.cpp
#include <map>
#include <iostream>
#include <string>

int main() {
    const std::map<std::string, int> mapCityPopulation {
        { "Beijing", 21'707'000 },
        { "London", 8'787'892 },
        { "New York", 8'622'698 }
    };
    
    for (auto&[city, population] : mapCityPopulation)
        std::cout << city << ": " << population << '\n';
}

In the loop body, you can safely use city and population variables.

Providing Structured Binding Interface for Custom Class

As mentioned earlier, you can provide Structured Binding support for a custom class.

To do that you have to define get<N>, std::tuple_size and std::tuple_element specialisations for your type.

For example, if you have a class with three members, but you’d like to expose only its public interface:

Chapter Chapter General Language Features/custom_structured_bindings.cpp
class UserEntry {
public:
    void Load() { }
    
    std::string GetName() const { return name; }
    unsigned GetAge() const { return age; }
private:
    std::string name;
    unsigned age { 0 };
    size_t cacheEntry { 0 }; // not exposed
};

The interface for Structured Bindings:

Chapter Chapter General Language Features/custom_structured_bindings.cpp
// with if constexpr:
template <size_t I> auto get(const UserEntry& u) {
    if constexpr (I == 0) return u.GetName();
    else if constexpr (I == 1) return u.GetAge();
}

namespace std {
    template <> struct tuple_size<UserEntry> : integral_constant<size_t, 2> { };

    template <> struct tuple_element<0,UserEntry> { using type = std::string; };
    template <> struct tuple_element<1,UserEntry> { using type = unsigned; };
}

tuple_size specifies how many fields are available, tuple_element defines the type for a specific element and get<N> returns the values.

Alternatively, you can also use explicit get<> specialisations rather than if constexpr:

template<> string get<0>(const UserEntry &u)  { return u.GetName(); }
template<> unsigned get<1>(const UserEntry &u) { return u.GetAge(); }

For a lot of types, writing two (or several) functions might be more straightforward than using if constexpr.

Now you can use UserEntry in a structured binding, for example:

UserEntry u;
u.Load();
auto [name, age] = u; // read access
std:: cout << name << ", " << age << '\n';

This example only allows read access of the class. If you want write access, then the class should also provide accessors that return references to members. Later you have to implement get with references support.

The code in this section used if constexpr, you can read more about this powerful feature in the next chapter: Templates: if constexpr.

Init Statement for if and switch

C++17 provides new versions of the if and switch statements:

if (init; condition)

And

switch (init; condition)

In the init section you can specify a new variable, similarly to the init section in for loop. Then check the variable in the condition section. The variable is visible only in if/else scope.

To achieve a similar result, before C++17, you had to write:

{   
    auto val = GetValue();   
    if (condition(val))    
        // on success  
    else   
        // on false... 
}

Please notice that val has a separate scope, without that it ‘leaks’ to enclosing scope.

Now, in C++17, you can write:

if (auto val = GetValue(); condition(val))    
    // on success  
else   
    // on false... 

Now, val is visible only inside the if and else statements, so it doesn’t ‘leak.’ condition might be any boolean condition.

Why is this useful?

Let’s say you want to search for a few things in a string:

const std::string myString = "My Hello World Wow";

const auto pos = myString.find("Hello");
if (pos != std::string::npos)
    std::cout << pos << " Hello\n"

const auto pos2 = myString.find("World");
if (pos2 != std::string::npos)
    std::cout << pos2 << " World\n"

You have to use different names for pos or enclose it with a separate scope:

{
    const auto pos = myString.find("Hello");
    if (pos != std::string::npos)
        std::cout << pos << " Hello\n"
}

{
    const auto pos = myString.find("World");
    if (pos != std::string::npos)
        std::cout << pos << " World\n"
}

The new if statement will make that additional scope in one line:

if (const auto pos = myString.find("Hello"); pos != std::string::npos)
    std::cout << pos << " Hello\n";

if (const auto pos = myString.find("World"); pos != std::string::npos)
    std::cout << pos << " World\n";

As mentioned before, the variable defined in the if statement is also visible in the else block. So you can write:

if (const auto pos = myString.find("World"); pos != std::string::npos)
    std::cout << pos << " World\n";
else
    std::cout << pos << " not found!!\n";

Plus, you can use it with structured bindings (following Herb Sutter code):

// better together: structured bindings + if initializer
if (auto [iter, succeeded] = mymap.insert(value); succeeded) {
    use(iter);  // ok
    // ...
} // iter and succeeded are destroyed here

In the above example, you can refer to iter and succeeded rather than pair.first and pair.second that is returned from mymap.insert.

As you can see, structured bindings and tuples allow you to create even more variables in the init section of the if-statement. But is the code easier to read that way?

For example:

string str = "Hi World";
if (auto [pos, size] = pair(str.find("Hi"), str.size()); pos != string::npos)
    std::cout << pos << " Hello, size is " << size;

We can argue that putting more code into the init section makes the code less readable, so pay attention to such cases.

Inline Variables

With Non-Static Data Member Initialisation introduced in C++11, you can now declare and initialise member variables in one place:

class User {
    int _age {0};
    std::string _name {"unknown"};
};

However, with static variables (or const static) you need a declaration and then a definition in the implementation file.

C++11 with constexpr keyword allows you to declare and define static variables in one place, but it’s limited to constant expressions only.

Previously, only methods/functions could be specified as inline, but now you can do the same with variables, inside a header file.

From the proposal P0386R2:

For example:

// inside a header file:
struct MyClass {
    static const int sValue;
};

// later in the same header file:
inline int const MyClass::sValue = 777;

Or even declaration and definition in one place:

struct MyClass {
    inline static const int sValue = 777;
};

Also, note that constexpr variables are inline implicitly, so there’s no need to use constexpr inline myVar = 10;.

An inline variable is also more flexible than a constexpr variable as it doesn’t have to be initialised with a constant expression. For example, you can initialise an inline variable with rand(), but it’s not possible to do the same with constexpr variable.

How Can it Simplify the Code?

A lot of header-only libraries can limit the number of hacks (like using inline functions or templates) and switch to using inline variables.

For example:

class MyClass {
    static inline int Seed(); // static method
};

inline int MyClass::Seed() {
    static const int seed = rand();
    return seed;
}

Can be changed into:

class MyClass {
    static inline int seed = rand();
};

C++17 guarantees that MyClass::seed will have the same value (generated at runtime) across all the compilation units!

constexpr Lambda Expressions

Lambda expressions were introduced in C++11, and since that moment they’ve become an essential part of modern C++. Another significant feature of C++11 is the constexpr specifier, which is used to express that a function or value can be computed at compile-time. In C++17, the two elements are allowed to exist together, so your lambda can be invoked in a constant expression context.

In C++11/14 the following code doesn’t compile, but works with C++17:

Chapter General Language Features/lambda_square.cpp
int main () {
    constexpr auto SquareLambda = [] (int n) { return n*n; };
    static_assert(SquareLambda(3) == 9, "");
}

Since C++17 lambda expressions (their call operator operator()) that follow the rules of standard constexpr functions are implicitly declared as constexpr.

What are the limitations of constexpr functions? Here’s a summary (from 10.1.5 The constexpr specifier [dcl.constexpr]):

  • they cannot be virtual
  • their return type shall be a literal type
  • their parameter types shall be a literal type
  • their function bodies cannot contain: asm definition, a goto statement, try-block, or a variable that is a non-literal type or static or thread storage duration

In practice, in C++17, if you want your function or lambda to be executed at compile-time, then the body of this function shouldn’t invoke any code that is not constexpr. For example, you cannot allocate memory dynamically or throw exceptions.

constexpr lambda expressions are also covered in the Other Changes Chapter and in a free ebook: C++ Lambda Story.

Capturing [*this] in Lambda Expressions

When you write a lambda inside a class method, you can reference a member variable by capturing this. For example:

Chapter General Language Features/capture_this.cpp
struct Test  {
    void foo() {
        std::cout << m_str  << '\n';
        auto addWordLambda = [this]() { m_str += "World"; };
        addWordLambda ();
        std::cout << m_str  << '\n';
    }
    
    std::string m_str {"Hello "};
};

In the line with auto addWordLambda = [this]() {... } we capture this pointer and later we can access m_str.

Please notice that we captured this by value… to a pointer. You have access to the member variable, not its copy. The same effect happens when you capture by [=] or [&]. That’s why when you call foo() on some Test object then you’ll see the following output:

Hello 
Hello World

foo() prints m_str two times. The first time we see "Hello", but the next time it’s "Hello World" because the lambda addWordLambda changed it.

How about more complicated cases? Do you know what will happen with the following code?

Returning a Lambda From a Method
#include <iostream>

struct Baz {
    auto foo() {
        return [=] { std::cout << s << '\n'; };
    }   
    std::string s;
};

int main() {
   auto f1 = Baz{"ala"}.foo();
   f1();
}

The code declares a Baz object and then invokes foo(). Please note that foo() returns a lambda that captures a member of the class.

A capturing block like [=] suggests that we capture variables by value, but if you access members of a class in a lambda expression, then it does this implicitly via the this pointer. So we captured a copy of this pointer, which is a dangling pointer as soon as we exceed the lifetime of the Baz object.

In C++17 you can write: [*this] and that will capture a copy of the whole object.

auto lam = [*this]() { std::cout << s; };

In C++14, the only way to make the code safer is init capture *this:

auto lam = [self=*this] { std::cout << self.s; };

Capturing this might get tricky when a lambda can outlive the object itself. This might happen when you use async calls or multithreading.

In C++20 (see P0806) you’ll also see an extra warning if you capture [=] in a method. Such expression captures the this pointer, and it might not be exactly what you want.

Nested Namespaces

Namespaces allow grouping types and functions into separate logical units.

For example, it’s common to see that each type or function from a library XY will be stored in a namespace xy. Like in the below case, where there’s SuperCompressionLib and it exposes functions called Compress() and Decompress():

namespace SuperCompressionLib {
    bool Compress();
    bool Decompress();
}

Things get interesting if you have two or more nested namespaces.

namespace MySuperCompany {
    namespace SecretProject {
        namespace SafetySystem {
            class SuperArmor {
                // ...
            };
            class SuperShield {
                // ...
            };
        } // SafetySystem 
    } // SecretProject 
} // MySuperCompany    

With C++17 nested namespaces can be written more compactly:

namespace MySuperCompany::SecretProject::SafetySystem {
    class SuperArmor {
    // ...
    };
    class SuperShield {
    // ...
    };
}

Such syntax is comfortable, and it will be easier to use for developers that have experience in languages like C# or Java.

In C++17 also the Standard Library was “compacted” in several places by using the new nested namespace feature:

For example, for regex.

In C++17 it’s defined as:

namespace std::regex_constants {
    typedef T1 syntax_option_type;
    // ...
}

Before C++17 the same was declared as:

namespace std {
    namespace regex_constants {
        typedef T1 syntax_option_type;
        // ...
    }
}

The above nested declarations appear in the C++ Specification, but it might look different in an STL implementation.

__has_include Preprocessor Expression

If your code has to work under two different compilers, then you might experience two different sets of available features and platform-specific changes.

In C++17 you can use __has_include preprocessor constant expression to check if a given header exists:

#if __has_include(<header_name>)
#if __has_include("header_name")

__has_include was available in Clang as an extension for many years, but now it was added to the Standard. It’s a part of “feature testing” helpers that allows you to check if a particular C++ feature or a header is available. If a compiler supports this macro, then it’s accessible even without the C++17 flag, that’s why you can check for a feature also if you work in C++11, or C++14 “mode”.

As an example, we can test if a platform has <charconv> header that declares C++17’s low-level conversion routines:

Chapter General Language Features/has_include.cpp
#if defined __has_include
#    if __has_include(<charconv>)
#        define has_charconv 1
#        include <charconv>
#    endif
#endif

std::optional<int> ConvertToInt(const std::string& str) {
    int value { };
    #ifdef has_charconv
        const auto last = str.data() + str.size();
        const auto res = std::from_chars(str.data(), last, value);
        if (res.ec == std::errc{} && res.ptr == last)
            return value;
    #else
        // alternative implementation...
    #endif
    
    return std::nullopt;
}

In the above code, we declare has_charconv based on the __has_include condition. If the header is not there, we need to provide an alternative implementation for ConvertToInt. You can check this code against GCC 7.1 and GCC 9.1 and see the effect as GCC 7.1 doesn’t expose the charconv header.

Note: In the above code we cannot write:

#if defined __has_include && __has_include(<charconv>)

As in older compilers - that don’t support __has_include we’d get a compile error. The compiler will complain that since __has_include is not defined and the whole expression is wrong.

Another important thing to remember is that sometimes a compiler might provide a header stub. For example, in C++14 mode the <execution> header might be present (it defines C++17 parallel algorithm execution modes), but the whole file will be empty (due to ifdefs). If you check for that file with __has_include and use C++14 mode, then you’ll get a wrong result.

__has_include along with feature testing macros might greatly simplify multiplatform code that usually needs to check for available platform elements.

Compiler support

Feature GCC Clang MSVC
Structured Binding Declarations 7.0 4.0 VS 2017 15.3
Init-statement for if/switch 7.0 3.9 VS 2017 15.3
Inline variables 7.0 3.9 VS 2017 15.5
constexpr Lambda Expressions 7.0 5.0 VS 2017 15.3
Lambda Capture of *this 7.0 3.9 VS 2017 15.3
Nested namespaces 6.0 3.6 VS 2015
has_include 5 Yes VS 2017 15.3