The constexpr Keyword
C++11 introduced the constexpr
keyword, which is used to declare functions or variables that can be evaluated at compile time. Functions or variables marked as constexpr
can be evaluated at compile time instead of runtime, thereby improving program performance and efficiency. The typical use cases for constexpr
include scenarios where values need to be calculated at compile time, such as in template metaprogramming or when declaring constant expressions.
In simple terms, constexpr
shifts the time of execution from runtime to compile time.
However, not all functions should be converted to constexpr
.
Here are some examples where constexpr
is not suitable
Situations where the value needs to be determined at runtime: If the value of a variable needs to be determined during program execution rather than at compile time, then
constexpr
is not suitable. For example, values obtained from user input or read from external files.Dependency on external state: If the calculation of a function or variable depends on external states that cannot be obtained at compile time, then
constexpr
is not appropriate. For example, accessing system time or file system status.Overly complex computation logic: If the computation logic of a function or variable is too complex to be fully unfolded and evaluated at compile time, then
constexpr
is not suitable.constexpr
functions are evaluated at compile time, and if the computation logic is too complex, it can increase compilation time and burden the compiler.
Validation
Below, we specifically validate the part where constexpr
increases compilation time.
The absolute time of calculation is not the focus, as factors like hardware can cause variation in compilation time on different computers. However, the relative time difference can indeed show the effect.
1
constexpr int result = fibonacci_constexpr(27);
Other 3 tests take: 3.9, 3.8, 3.4 second
1
int result = fibonacci_constexpr(27);
Other 3 tests take: 1.8, 1.9, 1.8 second
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <chrono>
constexpr int fibonacci_constexpr(int n) {
return (n <= 1) ? n : fibonacci_constexpr(n - 1) + fibonacci_constexpr(n - 2);
}
int fibonacci(int n) {
return (n <= 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
auto start = std::chrono::high_resolution_clock::now();
// Evaluated at compile time
constexpr int result = fibonacci_constexpr(27);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "Calculate at compile time: " << std::endl;
std::cout << "time: " << duration.count() << " (s)" << std::endl;
std::cout << "-----------------" << std::endl;
start = std::chrono::high_resolution_clock::now();
// Evaluated at runtime
int result2 = fibonacci(27);
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::cout << "Calculate at run time: " << std::endl;
std::cout << "time: " << duration.count() << " (s)" << std::endl;
return 0;
}
Results
The time spent calculating at runtime is significantly longer than when fibonacci_constexpr(27) is calculated at compile time. Since fibonacci_constexpr(27) is computed during compilation, it is simply a constant value substituted at runtime, resulting in very fast execution. Conversely, fibonacci calculates the value at runtime, as evidenced by the considerably longer calculation time.
It should be clear now, huh!