Constexpr
constexpr and Compile-Time Computation
Section titled “constexpr and Compile-Time Computation”constexpr enables compile-time computation in C++, allowing expressions to be evaluated during compilation rather than at runtime.
Introduction
Section titled “Introduction”constexpr was introduced in C++11 and significantly improved in C++14, C++17, and C++20. It enables:
- Variables that must be computed at compile time
- Functions that can execute at compile time
- Classes with compile-time constructors
- More powerful metaprogramming
constexpr Variables
Section titled “constexpr Variables”Basic Usage
Section titled “Basic Usage”#include <iostream>
// Compile-time constantsconstexpr int MAX_SIZE = 100;constexpr double PI = 3.14159265358979;constexpr int ARRAY_SIZE = MAX_SIZE + 1;
// String literals can be constexpr (C++17)constexpr const char* HELLO = "Hello";
// const vs constexprconst int constValue = 100; // Can be compile-timeconstexpr int constexprValue = 100; // Must be compile-time
int main() { // Array size must be compile-time constant int arr[constexprValue]; // OK: constexpr // int arr2[constValue]; // May not work if not truly constant
std::cout << "MAX_SIZE: " << MAX_SIZE << "\n"; std::cout << "PI: " << PI << "\n";
return 0;}When Runtime Values Are Needed
Section titled “When Runtime Values Are Needed”#include <iostream>
// This works: constexpr with known valueconstexpr int square(int x) { return x * x;}
int main() { // Runtime variable int n = 5;
// square(n) is evaluated at runtime (n is not constant) int result = square(n);
// square(5) can be evaluated at compile time constexpr int cs = square(5); // Computed at compile time
std::cout << "Runtime: " << result << "\n"; std::cout << "Compile-time: " << cs << "\n";
return 0;}constexpr Functions
Section titled “constexpr Functions”C++11 vs C++14
Section titled “C++11 vs C++14”// C++11 constexpr: limited to single return statementconstexpr int factorial11(int n) { return n <= 1 ? 1 : n * factorial11(n - 1);}
// C++14 constexpr: multiple statements allowedconstexpr int factorial14(int n) { int result = 1; for (int i = 2; i <= n; i++) { result *= i; } return result;}
// C++14: also allows local variablesconstexpr int fibonacci(int n) { if (n <= 1) return n; int a = 0, b = 1; for (int i = 2; i <= n; i++) { int temp = a + b; a = b; b = temp; } return b;}Recursive constexpr Functions
Section titled “Recursive constexpr Functions”#include <iostream>
constexpr int sumArray(int* arr, int size) { return size == 0 ? 0 : arr[0] + sumArray(arr + 1, size - 1);}
constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b);}
constexpr int power(int base, int exp) { return exp == 0 ? 1 : base * power(base, exp - 1);}
int main() { // All computed at compile time constexpr int f5 = factorial(5); constexpr int g = gcd(48, 18); constexpr int p = power(2, 10);
std::cout << "5! = " << f5 << "\n"; // 120 std::cout << "gcd(48,18) = " << g << "\n"; // 6 std::cout << "2^10 = " << p << "\n"; // 1024
return 0;}constexpr with Classes
Section titled “constexpr with Classes”constexpr Constructors
Section titled “constexpr Constructors”#include <iostream>
class Point { int x_, y_;
public: // constexpr constructor constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int x() const { return x_; } constexpr int y() const { return y_; }
constexpr Point operator+(const Point& other) const { return Point(x_ + other.x_, y_ + other.y_); }};
int main() { // Created at compile time! constexpr Point p1(1, 2); constexpr Point p2(3, 4); constexpr Point p3 = p1 + p2;
std::cout << "(" << p3.x() << ", " << p3.y() << ")\n"; // (4, 6)
return 0;}constexpr Member Functions
Section titled “constexpr Member Functions”#include <iostream>#include <array>
class Rectangle { int width_, height_;
public: constexpr Rectangle(int w, int h) : width_(w), height_(h) {}
constexpr int area() const { return width_ * height_; } constexpr int perimeter() const { return 2 * (width_ + height_); }
constexpr void scale(int factor) { width_ *= factor; height_ *= factor; }
// C++20: constexpr virtual functions virtual constexpr int getArea() const { return area(); }};
int main() { constexpr Rectangle r(3, 4); static_assert(r.area() == 12); static_assert(r.perimeter() == 14);
// Can also run at runtime Rectangle r2(5, 6); std::cout << "Area: " << r2.area() << "\n";
return 0;}static_assert with constexpr
Section titled “static_assert with constexpr”#include <iostream>
constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1);}
int main() { // Compile-time assertions static_assert(factorial(0) == 1); static_assert(factorial(1) == 1); static_assert(factorial(5) == 120); static_assert(factorial(10) == 3628800);
// Also works with constexpr variables constexpr int f = factorial(6); static_assert(f == 720);
std::cout << "All static_asserts passed!\n";
return 0;}if constexpr (C++17)
Section titled “if constexpr (C++17)”Compile-Time Branching
Section titled “Compile-Time Branching”#include <iostream>#include <type_traits>
template<typename T>void process(const T& value) { if constexpr (std::is_integral_v<T>) { // Only compiled for integral types std::cout << "Integral: " << value * 2 << "\n"; } else if constexpr (std::is_floating_point_v<T>) { // Only compiled for floating point types std::cout << "Floating: " << value * 2.0 << "\n"; } else { // Only compiled for other types std::cout << "Other type\n"; }}
int main() { process(42); // Integral: 84 process(3.14); // Floating: 6.28 process("hello"); // Other type
return 0;}Practical Use Cases
Section titled “Practical Use Cases”#include <iostream>#include <vector>#include <string>
// Type erasure without runtime overhead for known typestemplate<typename T>class Container { std::vector<T> data_;
public: void add(const T& value) { data_.push_back(value); }
template<typename U = T> auto get(size_t index) const -> std::enable_if_t<std::is_arithmetic_v<U>, U> { return data_[index]; }
template<typename U = T> auto get(size_t index) const -> std::enable_if_t<!std::is_arithmetic_v<U>, const U&> { return data_[index]; }};
int main() { Container<int> ints; ints.add(1); ints.add(2); std::cout << ints.get(0) << "\n"; // 1
Container<std::string> strings; strings.add("hello"); std::cout << strings.get(0) << "\n"; // hello
return 0;}constexpr std::array
Section titled “constexpr std::array”#include <iostream>#include <array>
// Sort array at compile timetemplate<typename T, std::size_t N>constexpr std::array<T, N> sortArray(std::array<T, N> arr) { for (std::size_t i = 0; i < N - 1; i++) { for (std::size_t j = 0; j < N - i - 1; j++) { if (arr[j] > arr[j + 1]) { auto temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr;}
int main() { // Initialize at compile time constexpr std::array<int, 5> unsorted = {5, 2, 8, 1, 9};
// Sort at compile time! constexpr auto sorted = sortArray(unsorted);
// All computed at compile time static_assert(sorted[0] == 1); static_assert(sorted[4] == 9);
for (int x : sorted) { std::cout << x << " "; } std::cout << "\n";
return 0;}consteval (C++20)
Section titled “consteval (C++20)”Forcing Compile-Time Evaluation
Section titled “Forcing Compile-Time Evaluation”#include <iostream>
// Must be evaluated at compile timeconsteval int square(int x) { return x * x;}
// Can be runtime or compile-timeconstexpr int cube(int x) { return x * x * x;}
int main() { // Both work at compile time constexpr int s = square(5); // OK constexpr int c = cube(5); // OK
// Runtime values work with constexpr but not consteval int n = 10; int sr = square(n); // Error! n is not constant expression int cr = cube(n); // OK - evaluated at runtime
std::cout << s << " " << c << " " << cr << "\n";
return 0;}constexpr Dynamic Allocation (C++20)
Section titled “constexpr Dynamic Allocation (C++20)”#include <iostream>#include <memory>
// C++20 allows dynamic allocation in constexpr functionsconstexpr int* createArray() { // Can allocate in constexpr context! int* p = new int[5]{1, 2, 3, 4, 5}; return p;}
constexpr int sumArray(const int* p, std::size_t size) { int sum = 0; for (std::size_t i = 0; i < size; i++) { sum += p[i]; } return sum;}
int main() { // Both compile-time constexpr auto p = createArray(); constexpr int sum = sumArray(p, 5);
static_assert(sum == 15);
std::cout << "Sum: " << sum << "\n";
// Remember to free! delete[] p;
return 0;}constexpr Lambda (C++17)
Section titled “constexpr Lambda (C++17)”#include <iostream>#include <algorithm>
int main() { // constexpr lambda in C++17 constexpr auto square = [](int x) { return x * x; };
// Can use in constant expressions constexpr int result = square(5); static_assert(result == 25);
// Use with std::sort for compile-time sorting constexpr std::array arr = {5, 2, 8, 1, 9};
std::array sorted = arr; std::sort(sorted.begin(), sorted.end(), [](int a, int b) { return a < b; });
for (int x : sorted) { std::cout << x << " "; } std::cout << "\n";
return 0;}Best Practices
Section titled “Best Practices”- Use constexpr for values that should be compile-time constants
- Prefer constexpr over #define for constants
- Use consteval for functions that MUST be compile-time (C++20)
- Use if constexpr for type-dependent code (C++17)
- Mark constructors constexpr when possible
- Use static_assert for compile-time validation
Key Takeaways
Section titled “Key Takeaways”constexprenables compile-time computation- C++14 significantly expanded constexpr capabilities
if constexprenables compile-time branching (C++17)constevalforces compile-time evaluation (C++20)- constexpr lambdas available in C++17
- Use static_assert to verify compile-time results
- Enables powerful metaprogramming without templates