C++ Constexpr Functions
Compile-time computation for maximum performance
⚡ What are Constexpr Functions?
Constexpr functions can be evaluated at compile-time when given constant expressions as arguments. This means calculations happen during compilation, not runtime, resulting in faster program execution and compile-time constants.
// Constexpr function evaluated at compile-time
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// factorial(5) calculated during compilation = 120
Compile-time Result:
factorial(5) = 120 (computed before program runs!)
Constexpr Benefits
Performance
Zero runtime cost for constant expressions
constexpr int square(int x) { return x*x; }
Compile-time
Calculations done during compilation
constexpr int result = square(10); // = 100
Array Sizes
Can be used for array dimensions
int arr[square(5)]; // Array of size 25
Dual Nature
Works at compile-time and runtime
// Compile-time or runtime based on context
🔹 Basic Constexpr Functions
constexpr functions are evaluated at compile-time if their arguments are constant expressions,
enabling performance optimizations. They can be used in contexts requiring constants, like array sizes
or template arguments. A basic constexpr function looks like
constexpr int square(int x) { return x * x; }. The function must satisfy certain restrictions
(C++11/14/17 relaxed these). Using constexpr improves performance by moving calculations to compilation
and ensures errors are caught earlier.
#include <iostream>
using namespace std;
// Simple constexpr functions
constexpr int add(int a, int b) {
return a + b;
}
constexpr int multiply(int a, int b) {
return a * b;
}
constexpr bool isEven(int n) {
return n % 2 == 0;
}
int main() {
// These are computed at compile-time
constexpr int sum = add(10, 20); // = 30
constexpr int product = multiply(4, 5); // = 20
constexpr bool even = isEven(8); // = true
cout << "Sum: " << sum << endl;
cout << "Product: " << product << endl;
cout << "Is 8 even? " << (even ? "Yes" : "No") << endl;
// Can also be used at runtime
int x = 15, y = 25;
cout << "Runtime sum: " << add(x, y) << endl;
return 0;
}
Output:
Sum: 30
Product: 20
Is 8 even? Yes
Runtime sum: 40
🔹 Recursive Constexpr Functions
Recursive constexpr functions allow compile-time computation of factorial, Fibonacci numbers,
and other recursive algorithms. Since C++14, constexpr functions can contain loops and
multiple returns, making recursion straightforward. Example:
constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n-1); }. These functions are
powerful for meta-programming, generating lookup tables, or defining constants derived from complex formulas, all
without runtime overhead.
#include <iostream>
using namespace std;
// Recursive constexpr factorial
constexpr long long factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// Recursive constexpr Fibonacci
constexpr long long fibonacci(int n) {
return (n <= 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
// Recursive constexpr power function
constexpr long long power(int base, int exp) {
return (exp == 0) ? 1 : base * power(base, exp - 1);
}
int main() {
// All computed at compile-time!
constexpr auto fact5 = factorial(5); // 120
constexpr auto fib10 = fibonacci(10); // 55
constexpr auto pow23 = power(2, 8); // 256
cout << "5! = " << fact5 << endl;
cout << "10th Fibonacci = " << fib10 << endl;
cout << "2^8 = " << pow23 << endl;
return 0;
}
Output:
5! = 120
10th Fibonacci = 55
2^8 = 256
🔹 Constexpr for Array Sizes
constexpr functions can compute array sizes at compile-time, ensuring type safety and avoiding
macros. Instead of #define SIZE 100, use
constexpr int getSize() { return 100; } then int arr[getSize()];. This integrates size
calculations directly into the type system. You can base sizes on calculations or template parameters. Since the
size is a constant expression, it works with standard arrays and std::array, providing bounds checking
and better integration with STL algorithms.
#include <iostream>
using namespace std;
constexpr int calculateSize(int rows, int cols) {
return rows * cols;
}
constexpr int getMaxSize() {
return 100;
}
int main() {
// Array sizes determined at compile-time
constexpr int matrixSize = calculateSize(5, 4); // 20
constexpr int bufferSize = getMaxSize(); // 100
int matrix[matrixSize]; // Array of 20 integers
char buffer[bufferSize]; // Array of 100 characters
cout << "Matrix size: " << matrixSize << endl;
cout << "Buffer size: " << bufferSize << endl;
cout << "Matrix array size: " << sizeof(matrix)/sizeof(int) << endl;
cout << "Buffer array size: " << sizeof(buffer) << endl;
return 0;
}
Output:
Matrix size: 20
Buffer size: 100
Matrix array size: 20
Buffer array size: 100
🔹 Constexpr vs Regular Functions
constexpr functions offer the unique advantage of compile-time evaluation, eliminating runtime
cost for constant inputs. A regular function always runs at runtime. If a constexpr
function is called with non-constant arguments, it executes at runtime like a regular function. This dual nature
provides flexibility. Use constexpr to shift work to compile-time, reducing executable size and
improving performance. Modern C++ trends toward making more standard library functions constexpr (e.g.,
std::sort in C++20), enabling faster, safer code.
#include <iostream>
#include <chrono>
using namespace std;
// Regular function
int regularPower(int base, int exp) {
int result = 1;
for (int i = 0; i < exp; i++) {
result *= base;
}
return result;
}
// Constexpr function
constexpr int constexprPower(int base, int exp) {
int result = 1;
for (int i = 0; i < exp; i++) {
result *= base;
}
return result;
}
int main() {
const int iterations = 1000000;
// Compile-time calculation (zero runtime cost)
constexpr int compileTimeResult = constexprPower(2, 10); // = 1024
// Runtime calculation timing
auto start = chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; i++) {
volatile int result = regularPower(2, 10); // Prevent optimization
}
auto end = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::microseconds>(end - start);
cout << "Compile-time result: " << compileTimeResult << endl;
cout << "Runtime calculation took: " << duration.count() << " microseconds" << endl;
cout << "Compile-time calculation took: 0 microseconds!" << endl;
return 0;
}
Typical Output:
Compile-time result: 1024
Runtime calculation took: 3500 microseconds
Compile-time calculation took: 0 microseconds!