Understanding constexpr - Compile-time vs Runtime Evaluation
constexpr
0. Preface
I’ll be honest - constexpr
used to keep me up at night! It was so confusing that I couldn’t stand it anymore, so I decided to write this comprehensive guide.
constexpr
is supported in Modern C++ (C++11 and above). The specification of constexpr
has been evolving as the STL versions progress.
After looking at various lecture materials and use cases, it seems like everyone uses it differently and understands it differently. While the original author of constexpr
certainly had a clear intention, it feels quite different from the initial concept now.
However, there’s one thing everyone commonly asks:
“How is it different from const?”
const
is simply constant - once compiled, the data becomes immutable during runtime.
constexpr
has a similar intention. Programmers use it to ensure that variables or functions are determined at compile time.
But here’s where constexpr
gets a bit ambiguous: variables or functions declared with constexpr
can be determined either during compilation OR during runtime!
cpp reference c++1x constexpr
Let’s dive into some examples to explore this ambiguity…
1. Regular Example
#include <iostream>
int fibonacci(int n){
if (n >= 2)
return fibonacci(n-1) + fibonacci(n-2);
else
return n;
}
int main(){
std::cout << fibonacci(10) << '\n';
}
Here’s a simple Fibonacci sequence calculator.
This code calculates the value at runtime after compilation.
$ /usr/bin/time ./fibonacci
102334155
0.56user 0.00system 0:00.56elapsed 100%CPU (0avgtext+0avgdata 3324maxresident)k
0inputs+0outputs (0major+126minor)pagefaults 0swaps
Since the value is calculated at runtime, it takes quite a bit of time.
2. Template Approach
What if we want the efficiency of compile-time calculation? Let’s try C++ Template Meta Programming:
#include <iostream>
template <int N>
struct fibonacci
{
static int64_t const value = fibonacci<N-1>::value + fibonacci<N-2>::value;
};
template<>
struct fibonacci<0>
{
static int64_t const value = 0;
};
template<>
struct fibonacci<1>
{
static int64_t const value = 1;
};
int main(){
std::cout << fibonacci<40>::value << '\n';
}
Using C++ template techniques, the compiler internally generates code and does the work at compile time.
So fibonacci<40>::value is determined at compile time.
$ /usr/bin/time ./fibonacci_template
102334155
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 3388maxresident)k
0inputs+0outputs (0major+125minor)pagefaults 0swaps
Since the value was calculated at compile time, the execution time is super fast!
3. constexpr
So what happens when we use constexpr?
#include <iostream>
constexpr int fibonacci(int n){
return n>=2 ? fibonacci(n-1) + fibonacci(n-2): n;
}
template<int N>
struct constN{
constN(){ std::cout << N << '\n';}
};
int main(){
constN<fibonacci(40)> a; // Compile time
//std::cout << fibonacci(40) << '\n'; //Runtime
}
$ /usr/bin/time ./fibonacci_constexpr
102334155
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 3412maxresident)k
0inputs+0outputs (0major+125minor)pagefaults 0swaps
Again, fast execution because the value was calculated at compile time!
Here’s the interesting part: constexpr can be used in both forms.
If I uncomment the line in the same code above, the value gets determined at runtime and takes much longer:
$ /usr/bin/time ./fibonacci_constexpr
102334155
0.55user 0.00system 0:00.55elapsed 99%CPU (0avgtext+0avgdata 3376maxresident)k
0inputs+0outputs (0major+125minor)pagefaults 0swaps
4. Conclusion
As we’ve seen, constexpr
can be used at both compile time and runtime.
If the constexpr
requirements aren’t met during compilation, it automatically falls back to runtime calculation.
Using constexpr
gives you the best of both worlds:
- You can have values determined at compile time for efficiency
- You can still use debuggers to inspect runtime values at breakpoints
My conclusion: constexpr
is a technique that shows the programmer’s intention that a value could be determined at compile time, but doesn’t guarantee it will be.
It’s like saying “Hey compiler, if you can figure this out at compile time, please do. If not, that’s okay too - just calculate it at runtime.”
Pretty neat, right? 🚀
Share this article
Found this helpful? Share it with your network
관련 글
CV-qualifiers Explained - Understanding const and volatile
Understanding L-Values, R-Values & Move Semantics in Modern C++
CV-qualifiers
Join the Discussion
Share your thoughts and connect with other readers
댓글
GitHub 계정으로 로그인하여 댓글을 남겨보세요. 건설적인 의견과 질문을 환영합니다!
댓글을 불러오는 중...
댓글을 불러올 수 없습니다.