There are several statements in C++ whose syntax was modified in recent versions of the standard. I refer here to the if and switch statements that were modified in C++17 to include initializing statements, and the range-based for loop that supports the same as of C++20. Their general form is shown in the following table:
C++17 | if(init;opt condition) |
C++17 | switch(init;opt condition) |
C++20 | for(init;opt declaration : initializer) |
The init
part is optional and, if missing, then we have the regular (or traditional) form of the statement.
I am going to show a few examples here, but before I go further, I must make a small comment. There is no such thing as foreach in C++. At least, not as a keyword/statement. There is the concept of a for each statement, but it’s called range-based for loop. I always disliked this very long name, and I prefer to call it, at least in simplified context, a “foreach”.
if statements
Let’s start with if statements. The following listing shows snippets before C++17.
auto val = get_value(); if(val > 0) { std::cout << "processing " << val << '\n'; } else { std::cout << "value " << val << " is illegal\n"; } std::set<int> n{ 1,2,3,5,8 }; auto pos = n.find(3); if (pos != std::end(n)) { /* do something */ }
And this is how these snippets can be simplified with init statements in C++17:
if (auto val = get_value(); val > 0) { std::cout << "processing " << val << '\n'; } else { std::cout << "value " << val << " is illegal\n"; } std::set<int> n{ 1,2,3,5,8 }; if (auto pos = n.find(3); pos != std::end(n)) { /* do something */ }
A statement of the form if(init; condition) statement;
is equivalent to the following:
{ init; if(condition) statement; }
Therefore, the variable, or variables (because you can define multiple variables of the same type, separated by comma) are declared within a scope that immediately encloses the if statement. This means they are available not only in the if statement but also in all ensuing else-if statements. These may have other init statements, but you cannot redeclare a variable declared in the same if. Here is another example:
if (auto a = get_value(); a > 0) std::cout << "processing A " << a << '\n'; else if(auto b = get_another_value(); b > 0) std::cout << "processing B " << b << '\n';
This code is transformed by the compiler into the following form:
{ auto a = get_value(); if (a > 0) { std::cout << "processing A " << a << '\n'; } else { auto b = get_another_value(); if(b > 0) { std::cout << "processing B " << b << '\n'; } } }
switch statements
The other statement that supports init statements in C++17 is the switch statement. It’s syntax is very similar to what we have seen before.
Before C++17, a switch statement would typically have the following form (this being a simple example):
auto option = get_option(); switch (option) { case 'a': /* add */ break; case 'd': /* del */ break; case 'l': /* list */ break; case 'q': /* quit */ break; default: /* error */ break; }
In C++17, this statement can be refactored to the following form:
switch (auto option = get_option(); option) { case 'a': /* add */ break; case 'd': /* del */ break; case 'l': /* list */ break; case 'q': /* quit */ break; default: /* error */ break; }
Like in the case of the if statement, the compiler introduces an enclosing scope, transforming the above snippet to the following form:
{ auto option = get_option(); switch (option) { case 'a': /* add */ break; case 'd': /* del */ break; case 'l': /* list */ break; case 'q': /* quit */ break; default: /* error */ break; } }
range-based for loop statements
The latest statement to support initialization statements is the range-based for loops. This change was introduced in C++20 and is very similar to what we have seen so far.
The following is a range-based for loop prior to C++20:
std::vector<int> get_values() { return { 1,2,3,5,8 }; } auto const& values = get_values(); for (auto const& v : values) std::cout << v << '\n';
In C++20, we can rewrite this to the following form:
for (auto const& values = get_values(); auto const& v : values) std::cout << v << '\n';
Again, the compiler transforms it by adding an enclosing scope:
{ auto const& values = get_values(); for (auto const& v : values) std::cout << v << '\n'; }
Actually, this is just a simplified form, since the compiler is transforming the range-based for loop at the same time. The more correct form is shown below:
{ auto const& values = get_values(); // with std::vector auto &&__range = values; auto __begin = values.begin(); auto __end = values.end(); for ( ; __begin != __end; ++__begin ) { auto const& v = *__begin; std::cout << v << '\n'; } }
This form with init statement can be handy when you iterate over a range but you also need the index of the elements of the range. Here is an example:
for (size_t index = 1; auto const& v : get_values()) std::cout << "value " << index++ << ": " << v << '\n';
Initialization statements for range-based for loops are available in GCC 9, Clang 8, and visual Studio 2019 16.5.
If you want to see how the compiler is transforming your code you can try your snippets at https://cppinsights.io/.