DEV Community

Hong Jiang
Hong Jiang

Posted on

Modern C++ in small pieces: constant expressions

#c

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 consists 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.

Top comments (0)