Let there be constants!

In the beginning, there was const. And people saw that const was good. And then the people said: let there be constexpr, and consteval, and constinit. And thus, starts this article about constant functions and variables in C++20, which I will try to keep short and concise.

const

const is a specifier that indicates that a global, local, or member variable or a function parameter cannot be modified after it is initialized. It can also be used to qualify a non-static member function; such a constant member function cannot modify the object state (unless the fields are declared mutable) and can only invoke other constant member functions. Such a function, however, is still evaluated at runtime.

The term const correctness refers to using constants whereever is possible. However, the exact placement of the const specifier has generated a great debate within the C++ community between those that advocate the use of it on the left or right side of the type. These alternatives are called East const and West const.

I have been using East const for many years and I believe it’s the better alternative. I will not get into the details here, but you can read more about it here: Join the East const revolution!

As of C++17, constants (and variables in general) can be declared inline. This makes it possible to define global constants in a header file, or initializing static const members of a class in a header, without risking to generate multiple definitions for the same symbol when the header is included in more than one translation unit.

constexpr

The constexpr specifier was introduced in C++11 to indicate that a variable or function can appear in a constant expression, which is an expression that can be evaluated at compile time.

Specifying constexpr for a function does not mean that the function is always evaluated at compile-time. This is done only when it’s possible. If invoked with arguments that are not constant expressions, the evaluation will only happen at runtime, as shown in the following example.

A constexpr specifier used in an object declaration implies const. A constexpr specifier used in a function or static member variable declaration implies inline. If any declaration of a function or function template has a constexpr specifier, then every declaration must contain the specifier.

constexpr can be used not only with variables and functions, but also member functions, constructors, and as of C++20 with virtual functions. There are various other changes in C++20 related to constexpr:

  • can use try-catch blocks in a constexpr function as long as no exception is thrown from the function;
  • it is possible to change the active member of a union inside constexpr;
  • it is possible to use dynamic_cast and polymorphic typeid in constant expressions;
  • std::string, std::vector, and other library types are constexpr;
  • std::is_constant_evaluated() added to allow to check if code is actually executed within a constant evaluation.

consteval

The consteval specifier is a new feature in C++20 that is used to specify that a function is an immediate function, which means that the function must always produce a constant expression. This has the implication that the function is only seen at compile time. Symbols are not emitted for the function, you cannot take the address of such a function, and tools such as debuggers will not be able to show them. In this matter, immediate functions are similar to macros.

A consteval specifier implies inline. If any declaration of a function or function template contains a consteval specifier, then all declarations of that function or function template must contain the specifier. A function that is consteval is a constexpr function, and must satisfy the requirements applicable to constexpr functions (or constexpr constructors).

constinit

Before getting to the constinit specifier, let’s talk about initialization. There are various forms of initialization in C++. Zero initialization sets the initial value of an object to zero. It happens in several situations:

Zero initialization is performed for every named variable with static or thread-local duration when constant-initialization does not happen and occurs before any other initialization.

Constant initialization sets the value of a static variable to a compile-time expression. It can have the following forms:

Constant initialization is performed instead of zero initialization. Together, zero-initialization and constant initialization are called static initialization and all other initialization is called dynamic initialization. All static initialization happens before any dynamic initialization.

Variables with static storage duration that have dynamic initiliazation could cause bugs that are hard to find. Consider two static objects, A and B, initialized in different translation units. If the initialization of one of the objects, let’s say B, depends on the other object (for intance by invoking a member of that object), then the initialization may succeed, if the other object is already initialized, or fail, if the object is not initialized already. This result depends on the order of the initialization of the translation units, which is not deterministic.

On the other hand, variables with static duration that have constant initialization are initialized at compile time and therefore can be safely used when performing dynamic initialization of translation units.

The constinit specifier can be applied to variables with static storage duration and requires the variable to have a constant initializer. This specifier helps communicating the intention to both the compiler and other programmers. The specifier can be used on any declaration of a variable; however, if it is present on some declaration but not on the initializing declaration, the program is ill-formed.

The following is an example from the P1143R2 paper.

Keep in mind that…

At most one of the constexpr, consteval, and constinit specifiers is allowed to appear within the same sequence of declaration specifiers.

See also

For more information about these topics see:

2 Replies to “Let there be constants!”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.