tags:
- Cpp
Consteval in C++ (ENG)
After learning about the constexpr
specifier, we have got some understandings about compile-time optimization. In this note, let's explore the consteval
specifier.
The consteval
specifier was introduced in C++20 and it declares a function as an "immediate function", meaning that every call to the function must produce a compile-time constant expression. This enforces that the function is always evaluated at compile-time.
You can put constexpr
anywhere, but consteval
is only allowed in function and function template declarations.
Our question here is: what's the different between constexpr
and consteval
? We have one main difference though. The constexpr
specifier does not provide a guarantee about function evaluation at compile-time, which means the the machine code may vary depending on the compiler. However, consteval
can give you a guarantee, ensuring Compile-Time Function Execution(CTFE).
For example, GCC may be strict about CTFE, making every possible constexpr
declared function inlined and a constant expression at compile-time. On the other hand, MSVC may be more lenient about CTFE, possibly not performing the work at compile-time. And if the constexpr function do executing at run-time which we would not like to, the compiler won't complain anything about it.
Let's dive into an example we are familiar with:
#include <iostream>
constexpr int add(int a, int b){
return a + b;
}
int main() {
int a = 100; // no constexpr declared this time
int b = 200; // no constexpr declared this time
int c = add(a, b); // no constexpr declared this time
return 0;
}
After compilation, let's see what we get:
add(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 100
mov DWORD PTR [rbp-8], 200
mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
call add(int, int)
mov DWORD PTR [rbp-12], eax
mov eax, 0
leave
ret
The add()
function is back, which means the function will execute at runtime. This is because the variables a
and b
are regular variables, while a constant expression evaluation cannot depend on any runtime-modifiable regular variables. Even with constexpr
, compiler cannot give you a guarantee about CTFE.
But with consteval
, you will get a compiler error instead. This is because the function add
is declared consteval
, but the compiler cannot determine the function value at compile-time with regular values.
#include <iostream>
consteval int add(int a, int b){
return a + b;
}
int main() {
const int a = 100; // constexpr declaration will also work
const int b = 200; // constexpr declaration will also work
int c = add(a, b); // okay
int c2 = add(a, add(a, b)); // okay
int d = 100; // error, must be constant expression
int e = 200; // error, must be constant expression
int f = add(d, e); // error, add(a, b) is not a constant expression
return 0;
}