My favourite C++17 language features

Programming

In 2017 a new standard of C++ language has been released. Unlike C++14, C++17 has introduced a lot of new features. C++14 was rather a supplement for C++11 (ok, it has introduced some completely new features, but most were improvements of what we had known from C++11). C++17 brought a lot of new possibilities.

In this post I'm going to tell you about new things in C++17 which I like the most, because I found them useful in my newest projects.

Structured bindings

Everyone who has ever used std::tuple in C++ and tuples builtin into Python can obviously see the difference between approach to this structure in both languages. Python supports tuples as a part of language syntax. C++ has special type in standard library. For me it was always much easier to decide on tuple in Python than in C++. One cool thing about tuples in Python is extracting their values:

1 b = 2, "string", 42 # tuple with three elements of different types
2 x, y, z = b # x, y, z are now elements of tuple b
3 b[1] # extracts second element of tuple b

In C++ before release of C++17 standard there were two ways of doing it:

1 auto a = std::make_tuple(1, 2, "string");
2 int x, y;
3 std::string z;
4 std::tie(x, y, z) = a; // extracting tuple's values to variables
5 
6 std::get<1>(a); // extracting second element

We can easily see that in ,,old'' C++ (C++14) it was not very comfortable to extract data from tuple. Programmers often prefered using own simple structures to use std::tuple type.

What has changed in C++17? The answer is structured bindings. Structured bindings has been described as decomposition in P0144R0. Later, in P0615R0 its name was changed to structured bindings. Syntax has been stated in P0217R3.

Making long story short, new C++ syntax element lets us to make declaration and extract data from tuple into newly declared variables. This is how it looks like:

1 auto a = std::make_tuple(1, 2, "string");
2 auto [x, y, z] = a; // structured bindings

What is great about structured bindings is that we can make our own types support it. It's not complicated, because we have to create specialization for some types from std namespace: struct tuple_size, struct tuple_element and function get.

I hope that tuples will be seen in C++ code more often when compilers versions supporting C++17 will become more common.

Fold expressions

Have you ever used variadic templates? Using template parameters' pack in most cases required recursive calls. It's not bad solution, but in my opinion not sufficient for such powerful language. Fold expressions is a language tool which lets us accumulate values of template parameters pack using binary operator. There are four variants of fold experssion:

  1. unary right fold
  2. unary left fold
  3. binary right fold
  4. binary left fold

In first and second one parameter pack and operator is used. In third and fourth we can also add init value. On the listing below you can see source code example in which unary right fold is used in makeValue function. On the other hand, makeValueOld is using recursive approach to unpack parameters.

 1 #include <cstdint>
 2 
 3 // C++17 - with fold expression
 4 template <typename... Args>
 5 uint32_t makeValue(Args... args) {
 6   return (args ^ ...);
 7 }
 8 
 9 // before C++17 - with recursion
10 uint32_t makeValueOld() {
11   return 0;
12 }
13 
14 template <typename First, typename... Args>
15 uint32_t makeValueOld(First f, Args... args) {
16   return f ^ makeValueOld(args...);
17 }
18 
19 // usage
20 int main() {
21  volatile int x = makeValue(1, 1 << 1, 1 << 2, 1 << 3);
22  volatile int y = makeValueOld(1, 1 << 1, 1 << 2, 1 << 3);
23  return 0;
24 }

I have compiled this code with Linaro's GCC 7.2.1 for ARM. Without optimization, version with fold expression has all operations inline. Old style function has recursive calls. In case of -O2 optimization GCC executes both these functions at compile time.

What I like very much about new C++ standards are constexpr. In C++17 they became much more powerful than in C++14, but I'll talk about it later. Now I want to tell that defining both makeValue and makeValueOld as constexpr functions, results in ASM presented below.

1 movs    r3, #15
2 str     r3, [r7, #4]
3 movs    r3, #15
4 str     r3, [r7]

So if you use constexpr both approaches are equally efficient, because they are executed at compile time. However, version with fold expression looks much clearer in my opinion.

Second group are binary left and right folds. They let us to use initial value to which all parameters will be accumulated. It can be used to call overloaded operator on an object.

1 template <typename... Args>
2 constexpr void output(std::ostream& stream, Args&&... args) {
3     (stream << ... << args) << std::endl;
4 }

Difference between right and left folds is in order in which operations are applied. In right folds parameters are accumulated from last (right-most) to first (left-most). In left folds order is reverse. In output function binary left fold has been used. It means that first argument is passed to operator<< of std::ostream as first.

It will be much clearer if you look at following explanation (\(A\) is a pack of parameters, as an operator I will use \(+\), but it can be any binary operator supported by C++ and \(Init\) will be an init expression in binary folds):

  1. unary right fold: \((A + ...)\) is expanded to: \(A_1 + (... + (A_{N-1} + A_N))\)
  2. unary left fold: \((... + A)\) is expanded to: \(((A_1 + A_2) + ...) + A_N)\)
  3. binary right fold: \((A + ... + Init)\) is expanded to: \(A_1 + (... + (A_{N-1} + (A_N + Init)))\)
  4. binary left fold: \((Init + ... + A)\) is expanded to: \((((Init + A_1) + A_2) + ...) + A_N\)

You can read more about folds at cppreference.com and N4295 paper.

Constexpr if conditions

Next improvement which I have found interesting is constexpr if. In fact these keywords are now used in different order, but this is the former syntax which is used as this feature's name ([stmt.if] in N4659).

Expressions marked with constexpr can be executed during compilation, but don't have to. It means that we can write code which will be executed during compilation only when all necessary data is available then.

What does if constexpr (this is current syntax; P0292R2) mean? It informs compiler that condition check must be performed at compile time.

What programmer gains? Depending on the condition's value, some piece of code can be removed. Suppose we have such function:

1 constexpr int x = 2;
2 
3 int f(int a, int b) {
4   if constexpr (x & 1) {
5     return a * b;
6   } else {
7     return a + b;
8   }
9 }

Removing register saving and loading instructions, we receive this assembly code (again it's ARM ASM generated with Linaro GCC 7.2.1):

1 ldr     r2, [r7, #4]
2 ldr     r3, [r7]
3 add     r3, r3, r2
4 mov     r0, r3

There are no branches (jumps), multiplications and other instructions that should be present if if wasn't a constexpr. I like constexpr if because it will let me to avoid using macros which are hard to debug.

Initialization statements in selection statements

Awareness of object's lifetime scopes often effects in such constructions:

1 {
2     std::fstream f{"some_file"};
3     if (f.is_open()) {
4         // do sth
5     }
6 }
7 // some_file has been already closed, f object destroyed
8 // and memory freed

This approach is absolutely correct. We can easily see where object of std::fstream class is closed and destroyed. We also can be sure that this piece of code will not leak memory if caught exception would occur.

New syntax of if and switch statements takes great thing from for loop construction: init phase. Code sample presented above can now be written this way:

1 if (std::fstream f{"some_file"}; f.is_open()) {
2     // do sth
3 }
4 // here f doesn't exist

Functionally it is the same as the example above, but it is shorter, doesn't require additional indentation level and is more intuitive (for those who knows new syntax ;-)).

Here is proposal document containing whole description: P0305R1.

Nested namespaces

Nested namespaces were always aesthetic problem for me. I used to define them this way.

1 namespace A {
2 namespace B {
3 
4 int i;
5 
6 } } // namespace A::B

C++17 will help me to do it better:

1 namespace A::B {
2 
3 int i;
4 
5 } // namespace A::B

For me it's meaningful improvement, because it makes code more readable. Nothing more, but it's enough. All formal things are in N4230 paper.

Others

These few changes are definitely my favourite ones. However C++17 introduced more interesting things. It is worth to mention std::byte definition. I used to use uint8_t as a byte representation in byte-oriented memory accesses, but it is not elegant solution. New type definition will now tell anyone reading code that in this place we are operating on bare memory - neither on integers nor characters (P0298R0). Other small change is removing register keyword which has never meant anything concrete (P0001R1). It was just hint for compiler to keep given variable in registers, but was never really respected. There are also other novelties which I think are really significant, but I have found the most useful these mentioned in this post.