constexpr
/ 4 min read
Table of Contents
constexpr is one of those C++11 features that keeps getting better with each standard. It lets you do computations at compile time, making your code both faster and safer.
What constexpr Actually Does
constexpr tells the compiler that a function or variable can be evaluated at compile time:
constexpr int factorial(int n) { return (n <= 1) ? 1 : n * factorial(n - 1);}
constexpr int result = factorial(5); // Computed at compile time// Same as writing: const int result = 120;The computation happens during compilation, not runtime.
constexpr Variables
The simplest use is for compile-time constants:
constexpr double PI = 3.14159265359;constexpr size_t BUFFER_SIZE = 1024;constexpr const char* VERSION = "1.0.0";
// These are guaranteed to be computed at compile timestd::array<int, factorial(4)> array; // Array of size 24Much better than #define macros because they’re type-safe and scoped properly.
When I Actually Use constexpr
Most of the time, I use constexpr for:
- Mathematical constants and calculations:
constexpr double degrees_to_radians(double degrees) { return degrees * PI / 180.0;}
constexpr double angle = degrees_to_radians(45); // Computed at compile time- Array sizes and compile-time configuration:
constexpr size_t max_connections() { return 100; // Could be more complex logic}
constexpr size_t thread_count() { return std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() : 4;}
std::array<Connection, max_connections()> connections;- Template parameters that need compile-time values:
template<size_t N>class FixedString { char data_[N + 1];public: constexpr FixedString(const char (&str)[N + 1]) { for (size_t i = 0; i <= N; ++i) { data_[i] = str[i]; } }};
constexpr auto greeting = FixedString("Hello"); // Type is FixedString<5>- Performance-critical calculations:
constexpr uint32_t hash_string(const char* str) { uint32_t hash = 5381; for (const char* p = str; *p; ++p) { hash = ((hash << 5) + hash) + *p; } return hash;}
// Hash computed at compile timeswitch (input_command) { case hash_string("start"): handle_start(); break; case hash_string("stop"): handle_stop(); break;}C++14 Relaxed Rules
C++14 made constexpr functions much more flexible:
// C++11 - very restrictive, single return statementconstexpr int old_style(int n) { return (n == 0) ? 0 : n + old_style(n - 1);}
// C++14 - allows loops, multiple statementsconstexpr int new_style(int n) { int result = 0; for (int i = 0; i <= n; ++i) { result += i; } return result;}The constexpr vs const Difference
const means “read-only after initialization”, constexpr means “computable at compile time”:
const int runtime_value = get_user_input(); // OK - const but not constexprconstexpr int compile_time = 42; // OK - constexpr (and implicitly const)
constexpr int bad_example = get_user_input(); // Error! Can't compute at compile timeReal World Example
Here’s how I use it for a configuration system:
struct Config { static constexpr size_t max_users = 1000; static constexpr size_t buffer_size = 8192; static constexpr double timeout_seconds = 30.0;
static constexpr size_t hash_table_size() { // Next power of 2 after max_users size_t size = 1; while (size < max_users * 2) { size <<= 1; } return size; }};
// All computed at compile timestd::array<User*, Config::max_users> user_array;std::array<UserBucket, Config::hash_table_size()> hash_table; // Size is 2048constexpr Constructors
Classes can have constexpr constructors too:
class Point { double x_, y_;public: constexpr Point(double x, double y) : x_(x), y_(y) {}
constexpr double distance_from_origin() const { return std::sqrt(x_ * x_ + y_ * y_); }};
constexpr Point origin(0, 0);constexpr Point p(3, 4);constexpr double dist = p.distance_from_origin(); // 5.0, computed at compile timeThe Pattern I Follow
I make functions constexpr when they:
- Only depend on their parameters (no global state)
- Don’t do I/O or allocate memory
- Could reasonably be computed at compile time
// Good candidates for constexprconstexpr bool is_power_of_two(size_t n) { return n && !(n & (n - 1)); }constexpr size_t align_up(size_t value, size_t alignment) { return (value + alignment - 1) & ~(alignment - 1);}
// Not good candidatesint read_config_value(); // Does I/Ovoid* allocate_memory(size_t); // Allocates memoryC++20 and Beyond
C++20 added even more constexpr support:
#include <vector>#include <algorithm>
constexpr std::vector<int> create_data() { // C++20 - constexpr std::vector! std::vector<int> vec{1, 3, 2, 4}; std::sort(vec.begin(), vec.end()); return vec;}constexpr keeps getting more capable with each standard. It’s one of the best ways to write both fast and safe code - the compiler does the work for you.