constexpr variables

constexpr on a variable is a stronger promise than const: not just "this won't change," but "this value is known at compile time." That unlocks contexts that specifically require a compile-time constant — array sizes, template non-type arguments, static_assert conditions.

const int n1 = getUserInput();     // fine — const, but not known until runtime
constexpr int n2 = 10;              // fine — a genuine compile-time constant
constexpr int n3 = n2 * 2;           // fine — depends only on other compile-time constants

int arr1[n1];    // error (in standard C++) — array bound needs a compile-time constant
int arr2[n2];    // fine — n2 qualifies
            
A constexpr variable must be initialized immediately, and only from other values the compiler can evaluate at compile time. Inside a class, only static members can be constexpr — a non-static member is only instantiated when an object is constructed, at runtime, which contradicts "known at compile time."

constexpr functions

A constexpr function can run at compile time when its arguments are themselves compile-time constants — called with runtime values, it just runs normally at runtime like any other function. Several restrictions follow directly from "must be evaluable at compile time": before C++20 it couldn't be virtual (virtual dispatch is inherently a runtime decision) — C++20 lifted this and allows constexpr virtual functions, as long as the actual call resolved at compile time doesn't require runtime polymorphism — it can (in the constant-evaluated path) only call other constexpr functions, and it can't read or write anything outside itself (no global mutable state, no I/O).

constexpr int factorial(int n) {
  return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr int f5 = factorial(5);   // computed at compile time — f5 is baked into the binary as 120
int x = getUserInput();
int fx = factorial(x);              // same function, but now evaluated at runtime — x isn't known until then
            
In C++11, a constexpr function body could contain no local variable declarations at all (only a single return, static_assert, and type aliases). C++14 is what introduced local variables inside constexpr functions in the first place, as long as they're initialized and not static or thread-local — including locals of user-defined type, provided that type's constructor is itself constexpr (which in turn requires that constructor to initialize every member of the class). That same C++14 relaxation is also what first allowed a constexpr function to return void (by making void a literal type) — this was not a C++20 change.

constexpr classes


class Point {
  int x, y;
public:
  constexpr Point(int x, int y) : x(x), y(y) {}   // constructor initializes every member — required
  constexpr int getX() const { return x; }
};

constexpr Point origin(0, 0);          // constructed entirely at compile time
constexpr int ox = origin.getX();      // also evaluated at compile time
            
A constexpr constructor is only valid if it initializes every data member — the compiler needs to fully account for the object's state to evaluate it at compile time, and a partially-initialized member would leave that state undefined.

static inside a class

A worked example, drawn straight from testing it: a base class tracks how many objects of it (and its derived classes) have ever been constructed, using one shared static counter.

class Shape {
public:
  Shape() { total++; }
  static int total;                       // declaration — one copy, shared by every Shape and every subclass
  void report(std::string label) { std::cout << label << ".total = " << total << '\n'; }
};
int Shape::total = 0;                      // definition — required exactly once, outside the class

class Circle : public Shape {};             // doesn't declare its own total — shares Shape's

Shape a, b;
Circle c;                                   // Circle's constructor implicitly calls Shape's, incrementing total too
a.report("a");   // a.total = 3
b.report("b");   // b.total = 3
c.report("c");   // c.total = 3 — same shared counter, not one per subclass
            
Static data members can't be initialized in a constructor — there's only one copy to begin with, shared across every instance, so "initializing per object" wouldn't mean anything. A static member function has no this and can only access other static members; a static function can't be virtual, const, or volatile, since all three of those qualify how a function relates to a specific object instance, and a static function isn't tied to one.
A static object declared inside a class is constructed once, unconditionally, even if it's never used. A static object declared inside a function is different: it's constructed the first time execution reaches its declaration, so if that function is never called, the object is never created — though the function still pays for a one-time initialization check on every call after the first.
The Shape/Circle counter above, as a full runnable program: TopNotchNote/cpp/constexpr_static_shape_counter.cpp

static outside a class: internal linkage

At file (global) scope, static means something different from inside a class: it restricts a function or variable's visibility to the current translation unit, making it invisible to the linker elsewhere — internal linkage. Without static, a free function or global variable has external linkage by default, visible to every other file the linker combines into the program.

// helpers.cpp
static void internalHelper() { /* only callable from within helpers.cpp */ }
void publicApi()              { /* callable from any file that declares it */ }
            
Internal linkage is the mechanism; the underlying rule it enforces is what "internal vs. external linkage" means generally: internal linkage keeps a name scoped to one translation unit, external linkage exposes it to the whole program. A function-local static behaves the opposite way when the enclosing function is inline with external linkage: the standard guarantees a single shared object across every translation unit that defines that inline function, even though the function itself may be compiled into multiple object files. That's precisely what makes the Meyers-singleton pattern safe to put in a header included from many .cpp files — see Design Patterns.

static_assert

The compile-time counterpart to a runtime assert — if the condition is false, the build fails immediately with your message, instead of the program misbehaving (or an assert catching it only when that code path actually executes). static_assert pairs naturally with constexpr and templates, since both let you express conditions the compiler can actually check up front.

template <typename T>
struct FixedBuffer {
  static_assert(std::is_trivially_copyable<T>::value, "FixedBuffer requires a trivially copyable type");
  T data[64];
};
            

where to go from here

Templates & Generics — static_assert and non-type template parameters together.
C++ Best Practices — struct padding, and how static members are excluded from sizeof.
C++ Fundamentals — internal vs. external linkage, introduced there and covered fully here.

reference

cppreference — constexpr specifier
cppreference — storage duration and linkage