consteval in C++20: A Beginner’s Guide to Immediate Functions
Optimizing C++: How to Shift Calculations from Runtime to Compile-Time.
C++11 introduced constexpr functions (with capabilities enhanced in C++17), are functions capable of being evaluated at compile time and produce a constant expression as its return value. (Check this POST for more in-depth details about constexpr functions)
The most important point is:
Just marking a a function as constexpr doesn’t guaranty that the function will be evaluated at compile time. It can be, but not must be. It depends on on how it is used.
Look at the below example:
#include <iostream>
constexpr int calculateArea(int l, int w) {
return l * w;
}
int main() {
constexpr int length{10};
constexpr int width{10};
// Will be evaluated at compile time
constexpr int compiletime_area{calculateArea(length, width)}; // Call-1
// Use user input length and width to calculate the area
int user_input_len;
std::cin >> user_input_len;
int user_input_wid;
std::cin >> user_input_wid;
// Will be evaluated at run-time
int runtime_area{calculateArea(user_input_len, user_input_wid)}; // Call-2
return 0;
}The assembly:
calculateArea(int, int): <---- This will also go into the final binary
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
imul eax, DWORD PTR [rbp-8]
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-4], 10
mov DWORD PTR [rbp-8], 10
mov DWORD PTR [rbp-12], 100. <-- Call-1 is replaced with result
as its evaluated at compiletime
lea rax, [rbp-20]
mov rsi, rax
mov edi, OFFSET FLAT:std::cin
call std::istream::operator>>(int&)
lea rax, [rbp-24]
mov rsi, rax
mov edi, OFFSET FLAT:std::cin
call std::istream::operator>>(int&)
mov edx, DWORD PTR [rbp-24]
mov eax, DWORD PTR [rbp-20]
mov esi, edx
mov edi, eax
call calculateArea(int, int) <-- Call-2 has function call and will
be evaluated at run-time
mov DWORD PTR [rbp-16], eax
mov eax, 0
leave
retImportant observation from the assembly:
Since the calculateArea evaluation can be compile-time or run-time depnding upon the context you can see the calculateArea(int, int) is present in the assembly and go into the final binary.
To achieve true compile time evaluation and get deterministic behaviour C++20 introduced consteval .
C++20 consteval:
C++20 introduced consteval functions are guaranteed to be evaluated during compile time and generate a constant expression result. If the evaluation of the function marked as consteval cannot be evaluated during compile time, compiler will through error and fail the compilation.
Since they are guaranteed to be evaluated during compile time they are also called Immediate Functions.
In Simple term:
All the dependency of aconsteval functionmust be constexpr. That means its parameters, any function call within its body and the return result, all must be known at compile time.
Lets look at the previous example and lets mark the calculateArea function as consteval :
#include <iostream>
consteval int calculateArea(int l, int w) {
return l * w;
}
int main() {
constexpr int length{10};
constexpr int width{10};
// Will be evaluated at compile time for sure
constexpr int compiletime_area{calculateArea(length, width)};
int var_len = 20;
int var_widht = 50;
// This will result in compilation error since var_len, var_widht are
// not constexpr and are runtime variables
constexpr int compiletime_area_1{calculateArea(var_len, var_widht)};
return 0;
}The assembly:
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 10
mov DWORD PTR [rbp-8], 10
mov DWORD PTR [rbp-12], 100
mov eax, 0
pop rbp
retconsteval function definitions never emit to the final binary:
Did you notice something from the assembly ? There is no assembly for calculateArea . Since consteval functions are for sure will be evaluated at compile-time so the actual function definition never emit to the final binary and all the function calls are actually replaced by the evaluation result.
Get Sagar’s stories in your inbox
Join Medium for free to get updates from this writer.
But have a look at the previous assembly when the function was declared as constexpr , the assembly has calculateArea(int, int) that will go into the final binary.
The result of the consteval function captured as a non constexpr can be modified and also the evaluation is guaranteed to happen at compile time:
So what i am trying to say you can understand from the following codes:
#include <iostream>
constexpr int calculateArea(int l, int w) {
return l * w;
}
int main() {
constexpr int normal_area{calculateArea(20,40)};
return 0;
}So in this example the calculateArea will be evaluated at compile-time.
What if i need to update the result normal_area in my main function.
The below code will fail as expected since constexpr variable are read-only.
#include <iostream>
constexpr int calculateArea(int l, int w) {
return l * w;
}
int main() {
constexpr int normal_area{calculateArea(20,40)};
normal_area++;
return 0;
}
// Compilation Error:
// error: cannot assign to variable 'normal_area' with const-qualified type 'const int'Ok then lets mark this normal_area as a non-constexpr. Then we can modify. But once we modify the normal_area to non-constexpr the calculateArea will no longer evaluated at compile-time. It will be evaluated at run-time.
#include <iostream>
constexpr int calculateArea(int l, int w) {
return l * w;
}
int main() {
int normal_area{calculateArea(20,40)};
normal_area++;
return 0;
}From the assembly you can see it will be evaluated at run-time:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 15, 0 sdk_version 15, 5
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
... Removed Not Important things...
bl __Z13calculateAreaii
; -- End function
.globl __Z13calculateAreaii ; -- Begin function _Z13calculateAreaii
.weak_definition __Z13calculateAreaii
.
; -- End function
.subsections_via_symbolsBut if you mark the function as consteval then if the calculateArea function follows all the constexpr rules it will be evaluated at compile-time even though the result you are capturing in a non-constexpr variable.
And since the result is captured in a non-constexpr variable you can modify it in your program. This is a very good additional support.
#include <iostream>
consteval int calculateArea(int l, int w) {
return l * w;
}
int main() {
// Wil be bound to be evaluated at compile-time
int normal_area{calculateArea(20,40)};
normal_area++; // Can modify
return 0;
}Have a look at the assembly of the program, you can clearly see the definition of calculateArea is not present in the assembly and the actual function call is replaced with the result 800.
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #16
.cfi_def_cfa_offset 16
mov w0, #0 ; =0x0
str wzr, [sp, #12]
mov w8, #800 ; =0x320
str w8, [sp, #8]
ldr w8, [sp, #8]
add w8, w8, #1
str w8, [sp, #8]
add sp, sp, #16
ret
.cfi_endproc
; -- End function
.subsections_via_symbolsconsteval functions can call constexpr functions but constexpr functions can not invoke consteval functions(there are special case its allowed if compile-time context is guaranteed by the constexpr):
consteval functionscan invokeconstexpr functions.constexprfunctions can be evaluated at compile-time, andconstevalrequires compile-time evaluation, so this is compatible.
Have a look at the example:
#include <iostream>
constexpr int add(int a, int b) {
return a + b;
}
consteval int multiplyAndAdd(int x, int y, int z) {
// consteval can call constexpr - this is VALID
return add(x, y) * z;
}
int main() {
constexpr int result = multiplyAndAdd(2, 3, 4); // OK: (2+3)*4 = 20
std::cout << result << std::endl;
return 0;
}
constexpr functionscan not invokeconsteval functionsin all context. Aconstexprfunction can be called at either compile-time OR runtime. If it were called at runtime, it would try to invokeconsteval functions, butconstevalfunctions don't exist at runtime - they're compile-time only.
#include <iostream>
consteval int square(int x) {
return x * x;
}
constexpr int sumOfSquares(int a, int b) {
// ERROR: constexpr cannot call consteval
return square(a) + square(b); // Compilation error!
}
int main() {
constexpr int result = sumOfSquares(3, 4);
std::cout << result << std::endl;
return 0;
}
```
**Error message:**
```
error: call to consteval function 'square' is not a constant expressionA
constexpr functionCAN call aconsteval functionbut only in a compile-time context (immediate context).
#include <iostream>
#include <array>
consteval int square(int x) {
return x * x;
}
constexpr int sumOfSquares(int a, int b) {
return square(a) + square(b); // Calls consteval
}
int main() {
// CASE 1: constexpr variable (forces compile-time evaluation)
constexpr int result = sumOfSquares(3, 4); // OK: 9 + 16 = 25
std::cout << "Result: " << result << std::endl;
// CASE 2: Constant expressions (require compile-time evaluation)
// Array size
int array[sumOfSquares(2, 3)]; // OK: array size = 4 + 9 = 13
std::cout << "Array size: " << sizeof(array) / sizeof(int) << std::endl;
// Template argument
std::array<int, sumOfSquares(1, 2)> templated_array; // OK: size = 1 + 4 = 5
std::cout << "Template array size: " << templated_array.size() << std::endl;
// static_assert
static_assert(sumOfSquares(5, 12) == 169, "5² + 12² should be 169"); // OK
// Switch case label
int value = 25;
switch(value) {
case sumOfSquares(3, 4): // OK: case 25
std::cout << "Matched case 25" << std::endl;
break;
default:
break;
}
// This would fail - runtime context
// int x = 10;
// int bad = sumOfSquares(x, 5); // ERROR!
return 0;
}
```
**Output:**
```
Result: 25
Array size: 13
Template array size: 5
Matched case 25C++20 std::is_constant_evaluated():
So now you know constexpr functions cannot call consteval function is all the context.
Additionally There are some cases where you need to know if a constexpr function will be evaluated at compile-time or run-time context, so that you can call appropriate compile-time or run-time inner functions.
To solve these problem, C++20 type_traitsprovided the std::is_constant_evaluated() .
For example look at the below code where i am trying to call the std::sqrt() method but it will fail since its a non-constexpr function.
#include <cmath>
constexpr double computeSquareRoot(double x) {
// ERROR: std::sqrt is not constexpr (before C++26)
return std::sqrt(x);
}
int main() {
constexpr double compile_time = computeSquareRoot(16.0); // Error!
double runtime = computeSquareRoot(16.0); // Would work if not constexpr
return 0;
}But i can modify this code using std::is_constant_evaluated() to provide a simple logic that can calculate square root if its compile-time context and use the faster std:sqrt function if its a run-time context like this:
#include <cmath>
#include <type_traits>
constexpr double computeSquareRoot(double x) {
if (std::is_constant_evaluated()) {
// Compile-time: Use manual implementation (constexpr-compatible)
double guess = x / 2.0;
for (int i = 0; i < 10; ++i) {
guess = (guess + x / guess) / 2.0;
}
return guess;
} else {
// Runtime: Use optimized library function
return std::sqrt(x); // Fast hardware instruction
}
}
int main() {
constexpr double compile_time = computeSquareRoot(16.0); // Uses manual calculation
double runtime = computeSquareRoot(16.0); // Uses std::sqrt
return 0;
}
std::is_constant_evaluated()Enables Callingconstevalfromconstexpr.
#include <type_traits>
consteval int square_consteval(int x) {
return x * x; // Compile-time only
}
int square_runtime(int x) {
return x * x; // Runtime version
}
constexpr int compute(int x) {
if (std::is_constant_evaluated()) {
return square_consteval(x); // OK! Only called at compile-time
} else {
return square_runtime(x); // Called at runtime
}
}
int main() {
constexpr int ct = compute(5); // Uses square_consteval
int rt = compute(5); // Uses square_runtime
return 0;
}Neat right. But wait C++23 provided even better way to do this.
C++23 if consteval:
if consteval is a C++23 feature that allows you to write different code paths for compile-time and runtime execution within the same function. It provides a clean, explicit way to detect whether your code is being evaluated at compile-time.
The problem that we have discussed previously of deciding if invocation of
consteval functionis possible or not from theconstexpr functioncan be solved easily with this feature.
Syntax:
if consteval {
// This block ONLY runs at compile-time
} else {
// This block ONLY runs at runtime
}In the below example based on
#include <type_traits>
#include <iostream>
// Compile-time only: slow but precise
consteval double compute_pi() {
double pi = 0.0;
for (int i = 0; i < 1000000; ++i) {
pi += (i % 2 == 0 ? 1.0 : -1.0) / (2 * i + 1);
}
return pi * 4;
}
constexpr double get_circle_area(double radius) {
double pi;
if (std::is_constant_evaluated()) {
pi = compute_pi(); // Compile-time: calculate precisely (slow, but happens once)
} else {
pi = 3.14159265359; // Runtime: use constant (instant)
}
return pi * radius * radius;
}
int main() {
// Compile-time: Spends time calculating pi precisely during compilation
constexpr double area1 = get_circle_area(5.0);
std::cout << "Compile-time area: " << area1 << std::endl;
// Runtime: Uses pre-defined constant, instant calculation
double radius = 10.0;
double area2 = get_circle_area(radius);
std::cout << "Runtime area: " << area2 << std::endl;
return 0;
}Summary:
From const to constexpr to consteval, C++ has steadily expanded what we can compute at compile-time. With consteval, we've reached the point where the line between "compile-time metaprogramming" and "regular programming" has all but disappeared. The future is compile-time—and it's here.
Use consteval when you mean it: computations that must happen at compile-time, validation that catches bugs before runtime, and optimizations that turn runtime cost into zero. Your binary—and your users—will thank you.
Hope you enjoyed reading this post !!! Feel free to give your valuable feedback in the comment !!!!
If you’re serious about passing technical interviews, try PracHub. It helped me structure my preparation with advanced mock sessions. [Check it out here] and start practicing. (Disclosure: This is an affiliate link).
Found this article helpful? Please Clap 👏 and follow for more C++ and system programming content.










