constexpr
and Constant ExpressionsA constant expression is an expression whose value cannot change and that can be evaluated at compile time. A literal is a constant expression. A const
object that is initialized from a constant expression is also a constant expression. As we’ll see, there are several contexts in the language that require constant expressions.
Whether a given object (or expression) is a constant expression depends on the types and the initializers. For example:
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression
Although staff_size
is initialized from a literal, it is not a constant expression because it is a plain int
, not a const int
. On the other hand, even though sz
is a const
, the value of its initializer is not known until run time. Hence, sz
is not a constant expression.
constexpr
VariablesIn a large system, it can be difficult to determine (for certain) that an initializer is a constant expression. We might define a const
variable with an initializer that we think is a constant expression. However, when we use that variable in a context that requires a constant expression we may discover that the initializer was not a constant expression. In general, the definition of an object and its use in such a context can be widely separated.
Under the new standard, we can ask the compiler to verify that a variable is a constant expression by declaring the variable in a constexpr
declaration. Variables declared as constexpr
are implicitly const
and must be initialized by constant expressions:
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
Although we cannot use an ordinary function as an initializer for a constexpr
variable, we’ll see in § 6.5.2 (p. 239) that the new standard lets us define certain functions as constexpr
. Such functions must be simple enough that the compiler can evaluate them at compile time. We can use constexpr
functions in the initializer of a constexpr
variable.
Generally, it is a good idea to use constexpr
for variables that you intend to use as constant expressions.
Because a constant expression is one that can be evaluated at compile time, there are limits on the types that we can use in a constexpr
declaration. The types we can use in a constexpr
are known as “literal types” because they are simple enough to have literal values.
Of the types we have used so far, the arithmetic, reference, and pointer types are literal types. Our Sales_item
class and the library IO and string
types are not literal types. Hence, we cannot define variables of these types as constexpr
s. We’ll see other kinds of literal types in § 7.5.6 (p. 299) and § 19.3 (p. 832).
Although we can define both pointers and reference as constexpr
s, the objects we use to initialize them are strictly limited. We can initialize a constexpr
pointer from the nullptr
literal or the literal (i.e., constant expression) 0
. We can also point to (or bind to) an object that remains at a fixed address.
For reasons we’ll cover in § 6.1.1 (p. 204), variables defined inside a function ordinarily are not stored at a fixed address. Hence, we cannot use a constexpr
pointer to point to such variables. On the other hand, the address of an object defined outside of any function is a constant expression, and so may be used to initialize a constexpr
pointer. We’ll see in § 6.1.1 (p. 205), that functions may define variables that exist across calls to that function. Like an object defined outside any function, these special local objects also have fixed addresses. Therefore, a constexpr
reference may be bound to, and a constexpr
pointer may address, such variables.
constexpr
It is important to understand that when we define a pointer in a constexpr
declaration, the constexpr
specifier applies to the pointer, not the type to which the pointer points:
const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
Despite appearances, the types of p
and q
are quite different; p
is a pointer to const
, whereas q
is a constant pointer. The difference is a consequence of the fact that constexpr
imposes a top-level const
(§ 2.4.3, p. 63) on the objects it defines.
Like any other constant pointer, a constexpr
pointer may point to a const
or a nonconst
type:
constexpr int *np = nullptr; // np is a constant pointer to int that is null
int j = 0;
constexpr int i = 42; // type of i is const int
// i and j must be defined outside any function
constexpr const int *p = &i; // p is a constant pointer to the const int i
constexpr int *p1 = &j; // p1 is a constant pointer to the int j