Modern C++ in small pieces: constant expressions

Modern C++ introduces the constexpr specifier to allow declaring compile-time constant expressions. This allows the programmer to explicitly specify what should be computed at compile time, instead of guessing what might be optimized away as constants by the compiler. A clearer boundary of compile time versus run time also makes other compile-time utilities easier to use.

const auto a = 10;
constexpr auto b = a + 1;  // const b = 11;
constexpr auto c = a + b;  // const c = 21;

All variables declared with constexpr are implicitly const and must be initialized with expressions computable at compile time, but the converse is not true. A const constant can be initialized with a run-time expression.

const auto a = std::rand();  // Ok.
constexpr auto b = a -1;  // compile error!

A function's return type can also be constexpr, as long as its body has no run-time dependencies, and it is only applied to constexpr arguments. The function is executed at compile time, and no code is generated for the function body.

constexpr auto smaller(auto a, auto b) {
  return a <= b ? a : b;
}

int main() {
  const auto MAX_GROUP_SIZE = 400;
  const auto MAX_USERS = 500;
  constexpr auto LIMIT = smaller(MAX_USERS, MAX_GROUP_SIZE);
  return 0;
}

Constant expression can also be used as conditions in if and switch statements. Because the condition is evaluated at compile time, the false branch is not compiled at all.

if constexpr (sizeof(void*)*CHAR_BIT == 64) {
  std::cout << "Compiled for 64-bit OS." << std::endl;
} else {
  std::cout << "Not compiled for 64-bit OS." << std::endl;
}

A constant expression can consist of other constant expressions, so you can potentially do some pretty complex computation at compile time. For example, the following program let the compiler compute the 10th Fibonacci number:

constexpr unsigned int static_fib(unsigned int n) {
  if (n <= 1)
    return n;
  else
    return static_fib(n-1) + static_fib(n-2);
}

int main() {
  std::cout << static_fib(10) << std::endl;
  return 0;
}

Note that functions in constant expressions have to be purely functional - there cannot be local variables, hence no loops. The code generated by the compiler for the above program is equivalent to:

int main() {
  std::cout << 55 << std::endl;
  return 0;
}

In legacy C++, it would require unsightly abuse of templates to achieve the same effect.