constexpr
and consteval
In this lesson, we’ll learn how to use the constexpr
and consteval
specifiers to ensure our expressions can be evaluated at compile time. Compile-time evaluation can significantly improve performance by removing expensive calculations from runtime to compile-time
It additionally unlocks advanced metaprogramming techniques, such as generating template parameters at compile time. We'll cover:
constexpr
to define compile-time constants and invoke functions at compile-time.consteval
in defining immediate functions that can only be called during compilation.constexpr
and consteval
to constructors, member functions, and operator overloads.constexpr
)We covered in our previous lessons, templates are instantiated at compile time. Because of this, any arguments we provide to templates must be known at compile time.
Below, we try to instantiate a template by providing an int
variable as an argument, which results in an error:
template <int SomeInt>
struct Resource {
int Value{SomeInt};
};
int main() {
int SomeValue{3};
Resource<SomeValue> A;
}
error C2971: 'Resource': template parameter 'SomeInt': 'SomeValue': a variable with non-static storage duration cannot be used as a non-type argument
As we introduced in our beginner lessons, we can flag a variable as constexpr
, letting the compiler ensure that the value of the variable can be determined at compile time:
template <int SomeInt>
struct Resource {
int Value{SomeInt};
};
int main() {
constexpr int SomeValue{3};
Resource<SomeValue> A;
}
When we do this, the compiler will prevent us from using any expression in the initialization that cannot be evaluated at compile time. For example, if we attempt to initialize a constexpr
variable using a function call, we’ll typically get a compilation error:
#include <iostream>
int GetInt() {
return 42;
}
int main() {
constexpr int SomeValue{GetInt()};
}
error C2131: expression did not evaluate to a constant
Similar to variables, we can mark functions as constexpr
:. This asks the compiler to ensure that the function can be evaluated at compile time and, as a result, makes the function usable in constexpr
 contexts:
constexpr int GetInt() {
return 42;
}
int main() {
// This now works
constexpr int SomeValue{GetInt()};
}
Value: 3
Because of this, the value returned by a constexpr
function can be used to provide an argument to a template:
#include <iostream>
constexpr int GetInt() {
return 42;
}
template <int SomeInt>
struct Resource {
int Value{SomeInt};
};
int main() {
Resource<GetInt()> A;
std::cout << "Value: " << A.Value;
}
Value: 42
When we have a constexpr
function, the compiler will prevent that function from doing anything that may not be possible at compile time.
For example, our function won’t be able to call other functions (or operators), unless those functions are also marked as constexpr
:
#include <iostream>
constexpr int GetInt() {
// We can't do this at compile time:
std::cout << "Getting an int";
return 42;
}
int main() {
constexpr int x{GetInt()};
}
error C3615: constexpr function 'GetInt' cannot result in a constant expression
failure was caused by call of undefined function or one not declared 'constexpr'
consteval
)When we mark a function as constexpr
, the compiler ensures that our function can be used at compile time. But, that does not mean our function can only be used at compile time.
It is possible to use constexpr
functions at run time:
#include <iostream>
int GetNumber() {
int x;
std::cout << "Enter A Number: ";
std::cin >> x;
return x;
}
constexpr int Add(int x, int y) {
return x + y;
}
int main() {
std::cout << Add(GetNumber(), GetNumber());
}
Enter A Number: 2
Enter A Number: 3
5
If we want a function to only be usable at compile time, we can mark it as consteval
. This imposes all the same restrictions as constexpr
but in addition, if an expression would result in a consteval
function being invoked at run time, we will instead get a compilation error:
#include <iostream>
int GetNumber() {
int x;
std::cout << "Enter A Number: ";
std::cin >> x;
return x;
}
consteval int Add(int x, int y) {
return x + y;
}
int main() {
std::cout << Add(GetNumber(), GetNumber());
}
error C7595: 'Add': call to immediate function is not a constant expression
Functions annotated consteval
are sometimes referred to as immediate functions. This is because, once the compiler sees code attempting to invoke a consteval
function, the compiler must perform the invocation immediately. It cannot be invoked later, at run time.
When working on larger projects, our build process can get quite elaborate. For example, our program may need to perform expensive calculations or format large amounts of supporting data.
By marking the functions that perform tasks like this consteval
, we make their purpose clearer. Additionally, the compiler ensures nobody uses these functions in a context that requires them be shipped to users and invoked at run time.
Even when an expression is evaluated at compile time, the result of that evaluation can still be available at run time. Below, we have the result of a consteval
function call being used to display output at run time:
#include <iostream>
consteval int Add(int x, int y) {
return x + y;
}
int main() {
std::cout << Add(1, 2);
}
3
To understand how this works, we can imagine the compiler replacing every consteval
expression in our code with the result of evaluating that expression.
In the previous example, Add(1, 2)
would get replaced with 3
at compile time. This means that at run time, it’s as if the body of our main
function was simply std::cout << 3;
and the Add()
function never existed.
Many object types can be constructed at compile time. We’ve seen examples of this using simple literals, and also standard library containers:
#include <utility>
int main() {
constexpr int SomeInt{42};
constexpr std::pair SomePair{42, true};
}
When working with our custom types, we can mark constructors as constexpr
or consteval
. As such, that constructor can be invoked at compile time, meaning instances of our user-defined type can be created at compile time:
struct SomeType {
constexpr SomeType(int init) : Value{init} {}
int Value;
};
int main() {
constexpr SomeType SomeObject{42};
}
Like any other function, the compiler will prevent us from doing anything within a constexpr
or consteval
constructor that might not be possible at compile time, such as calling a non-constexpr function or operator:
#include <iostream>
struct SomeType {
constexpr SomeType(int init) : Value{init} {
std::cout << "Hello World";
}
int Value;
};
int main() {
constexpr SomeType SomeObject{42};
}
error C3615: constexpr function 'SomeType::SomeType' cannot result in a constant expression
failure was caused by call of undefined function or one not declared 'constexpr'
Member functions can be made constexpr
or consteval
, with the same syntax and effect as any other function:
class SomeType {
public:
constexpr SomeType(int init) : Value{init} {}
constexpr int GetValue() const {
return Value;
}
private:
int Value;
};
int main() {
constexpr SomeType SomeObject{42};
constexpr int SomeInt{SomeObject.GetValue()};
}
Finally, operators can also be marked as constexpr
or consteval
:
struct SomeType {
constexpr SomeType(int init) : Value{init} {}
constexpr int operator+(int Other) const {
return Value + Other;
}
int Value;
};
int main() {
constexpr SomeType SomeObject{42};
constexpr int SomeInt{SomeObject + 2};
}
In this lesson, we learned that the constexpr
and consteval
keywords are used to enable compile-time evaluation of expressions, functions, and object constructions. Key points:
constexpr
variables and functions can be evaluated at compile-time if their initializers and expressions can be resolved as constant expressions.constexpr
functions can be invoked at both compile-time and runtime.consteval
functions, also known as immediate functions, can only be invoked at compile-time, preventing accidental runtime invocation.constexpr
or consteval
, enabling compile-time object construction and member function invocation.constexpr
or consteval
specifiers, allowing compile-time evaluation of operator expressions.Learn how to implement functionality at compile-time using constexpr
and consteval
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.