Constant expressions #

With constexpr, we can define an expression that can be evaluated at compile time. constexpr can be used for variables, functions, and user-defined types. An expression that is evaluated at compile time has a lot of advantages.

A constant expression:

  • can be evaluated at compile time
  • gives the compiler deep insight into the code
  • is implicitly thread-safe
  • can be constructed in the read-only memory (ROM-able)

constexpr - variables and objects #

If we declare a variable as constexpr, the compiler will evaluate it at compile time. This holds true not only for built-in types but also for instantiations of user-defined types. There are a few serious restrictions for objects in order to evaluate them at compile time.

To make our lives easier, we will use types built into the C++ library like bool, char, int, and double. We will call the remaining data types user-defined data types. These are all std::string types. User-defined types typically hold built-in types.

Variables #

By using the keyword, constexpr, a variable can be made a constant expression.

constexpr double myDouble= 5.2;

Therefore, we can use the variable in contexts that require a constant expression, e.g., defining the size of an array. This has to be done at compile time.

For the declaration of a constexpr variable, we have to keep a few rules in mind.

The variable:

  • is implicitly const.
  • has to be initialized.
  • requires a constant expression for initialization.

The rules make sense. If we evaluate a variable at compile-time, the variable can only depend on values that can be evaluated at compile time.

User-Defined types #

Objects are created by the invocation of the constructor. The constructor has a few special rules.

A constexpr constructor:

  • can only be invoked with constant expressions.

  • cannot use exception handling.

  • has to be declared as default or delete or the function body must be empty (C++11).

A constexpr user-defined type cannot have virtual base classes. It requires that each base object and each non-static member is initialized in the initialization list of the constructor, or directly in the class body. Consequently, it holds that each used constructor (e.g of a base class) has to be a constexpr constructor and that the applied initializers have to be constant expressions.

Example #

struct MyDouble{
  double myVal;
  constexpr MyDouble(double v): myVal(v){} 
  constexpr double getVal(){return myVal;}
};
  • The constructor has to be both empty and a constant expression.

  • The user-defined type can have methods that may or may not be constant expressions.

  • Instances of MyDouble can be instantiated at compile time.

Functions #

constexpr functions are functions that have the potential to run at compile time. This means we can perform a lot of calculations at compile time, with the results available at runtime and stored as a constant in the ROM. In addition, constexpr functions are implicitly inline.

A constexpr function can be invoked with a non-constexpr value. In this case, the function runs at runtime. A constexpr function is executed at compile-time when it is used in an expression that is evaluated at compile time (such as static_assert or the definition of a C-array) or when the result is requested at compile time:

constexpr auto res = constexprFunction()

For constexpr functions, there are a few restrictions:

The function:

  • has to be non-virtual.
  • has to have arguments and a return value of a literal type. constexpr variables are of literal types.
  • can only have one return statement.
  • must return a value.
  • will be executed at compile time if invoked within a constant expression.
  • can only have a function body consisting of a return statement.
  • must have a constant return value.
  • is implicitly inline.

Example #

constexpr int fac(int n)
  {return n > 0 ? n * fac(n-1): 1;}

constexpr int gcd(int a, int b){
  return (b== 0) ? a : gcd(b, a % b);

Functions with C++14 #

The syntax of constexpr functions was massively improved with the change from C++11 to C++14. In C++11, we had to remember which features we were able to use in a constexpr function. With C++14, we only have to remember which features we can’t use in a constexpr function.

constexpr functions in C++14

  • can have variables that have to be initialized by a constant expression.
  • can have loops.
  • cannot have static or thread_local data.
  • can have conditional jump instructions or loop instructions.
  • can have more than one instruction.

Example: Magic of constexpr functions #

Get hands-on with 1200+ tech skills courses.