Skip to content

Constexpr

constexpr enables compile-time computation in C++, allowing expressions to be evaluated during compilation rather than at runtime.

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
#include <iostream>
// Compile-time constants
constexpr 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 constexpr
const int constValue = 100; // Can be compile-time
constexpr 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;
}
#include <iostream>
// This works: constexpr with known value
constexpr 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;
}
// C++11 constexpr: limited to single return statement
constexpr int factorial11(int n) {
return n <= 1 ? 1 : n * factorial11(n - 1);
}
// C++14 constexpr: multiple statements allowed
constexpr int factorial14(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
// C++14: also allows local variables
constexpr 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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#include <iostream>
#include <vector>
#include <string>
// Type erasure without runtime overhead for known types
template<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;
}
#include <iostream>
#include <array>
// Sort array at compile time
template<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;
}
#include <iostream>
// Must be evaluated at compile time
consteval int square(int x) {
return x * x;
}
// Can be runtime or compile-time
constexpr 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;
}
#include <iostream>
#include <memory>
// C++20 allows dynamic allocation in constexpr functions
constexpr 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;
}
#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;
}
  1. Use constexpr for values that should be compile-time constants
  2. Prefer constexpr over #define for constants
  3. Use consteval for functions that MUST be compile-time (C++20)
  4. Use if constexpr for type-dependent code (C++17)
  5. Mark constructors constexpr when possible
  6. Use static_assert for compile-time validation
  • constexpr enables compile-time computation
  • C++14 significantly expanded constexpr capabilities
  • if constexpr enables compile-time branching (C++17)
  • consteval forces compile-time evaluation (C++20)
  • constexpr lambdas available in C++17
  • Use static_assert to verify compile-time results
  • Enables powerful metaprogramming without templates