Additions to compile-time facilities
With C++11, constexpr was added as a statement to the compiler that a variable, function, and so on, can be evaluated at compile time and optimized, reducing the complexity of the code at runtime and improving performance overall. In some cases, the compiler was smart enough to extend constexpr statements to other components, including branch statements, for example:
#include <iostream>
constexpr const auto val = true;
int main(void)
{
if (val) {
std::cout << "Hello World\n";
}
}
In this example, we have created a constexpr variable, and we only output Hello World to stdout if constexpr is true. Since, in this example, it's always true, the compiler will remove the branch from the code entirely, as shown here:
push %rbp
mov %rsp,%rbp
lea 0x100(%rip),%rsi
lea 0x200814(%rip),%rdi
callq 6c0 <...cout...>
mov $0x0,%eax
pop %rbp
retq
As you can see, the code loads a couple of registers and calls std::cout without checking whether val is true, since the compiler completely removed the code from the resulting binary. The issue with C++11 was that the author could assume that this type of optimization was taking place, when in fact it might not be.
To prevent this type of error, C++17 adds a constexpr if statement, which tells the compiler to specifically optimize the branch at compile time. If the compiler cannot optimize the if statement, an explicit compile-time error will occur, telling the user that optimization could not be done, providing the user with an opportunity to fix the issue (instead of assuming the optimization was taking place when in fact it might not be), for example:
#include <iostream>
int main(void)
{
if constexpr (constexpr const auto i = 42; i > 0) {
std::cout << "Hello World\n";
}
}
// > g++ scratchpad.cpp; ./a.out
// Hello World
In the preceding example, we have a more complicated if statement that leverages both a compile-time constexpr optimization as well as an if statement initializer. The resulting binary is as follows:
push %rbp
mov %rsp,%rbp
sub $0x10,%rsp
movl $0x2a,-0x4(%rbp)
lea 0x104(%rip),%rsi
lea 0x200809(%rip),%rdi
callq 6c0 <...cout...>
mov $0x0,%eax
leaveq
retq
As you can see, the branch has been removed from the resulting binary, and more specifically, if the expression was not a constant, the compiler would have thrown an error stating that this code could not be compiled as stated.
It should be noted that this result is not the same binary as previously as one might expect. It would appear that GCC 7.3 has some additional improvements to make in its optimization engine, as the constexpr i variable that was defined and initialized inside the binary was not removed (as stack space was allocated for i in this code when it didn't need to be).
Another compile-time change was a different version of the static_assert compile-time function. In C++11, the following was added:
#include <iostream>
int main(void)
{
static_assert(42 == 42, "the answer");
}
// > g++ scratchpad.cpp; ./a.out
//
The goal of the static_assert function is to ensure that certain compile-time assumptions are true. This is especially helpful when programming a system to do things such as making sure a structure is a specific size in bytes, or that a certain code path is taken, depending on the system you're compiling for. The problem with this assert was that it required the addition of a description that would be output during compile time, which likely just describes the assertion in English without providing any additional information. In C++17, another version of this assert was added, which removed the need for the description, as follows:
#include <iostream>
int main(void)
{
static_assert(42 == 42);
}
// > g++ scratchpad.cpp; ./a.out
//