Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A constexpr constructor is a constructor declared with the constexpr specifier, instructing the compiler that the object’s initialization can be evaluated at compile-time when provided with constant expression arguments. By defining at least one constexpr constructor (and meeting other structural requirements), a user-defined class qualifies as a literal type, allowing its instances to be evaluated during compilation.
class Point {
public:
    // constexpr constructor declaration
    constexpr Point(double x_val, double y_val) : x(x_val), y(y_val) {}

    constexpr double getX() const { return x; }

private:
    double x;
    double y;
};

// Compile-time instantiation
constexpr Point p(10.5, 20.0); 

Technical Requirements

For a constructor to be validly declared as constexpr, it must satisfy strict structural rules enforced by the compiler:
  1. Literal Type Parameters: Every parameter passed to the constructor must be a literal type (e.g., scalar types, references, or other literal classes).
  2. No Virtual Base Classes: The class being constructed cannot inherit from any virtual base classes.
  3. Initialization Constraints: Prior to C++20, the constructor was required to initialize all direct base classes and non-static data members either via the member initializer list or through default member initializers (in-class initializers). Since C++20, members can be left uninitialized initially, provided they are assigned a value before being read and before the constant evaluation of the constructor completes.
  4. Valid Constant Expression Path (Prior to C++23): Before C++23, the constructor was required to be capable of producing a valid constant expression for at least one set of arguments. If no invocation could ever result in a constant expression, the program was ill-formed (no diagnostic required). C++23 removed this requirement entirely, allowing a constructor to be marked constexpr regardless of whether a valid constant expression path exists.
  5. Function Try Blocks: The constructor cannot be a function try block (prior to C++20).

Evolution of Constructor Body Constraints

The rules governing the body of a constexpr constructor have been progressively relaxed across C++ standards:
  • C++11: The constructor body must be strictly empty ({}). All initialization logic must reside in the member initializer list or rely on default member initializers.
  • C++14: The body may contain statements, local variable declarations, if/switch branches, and loops. It may mutate the object being constructed, provided the mutations do not invoke non-constexpr functions or undefined behavior during constant evaluation.
  • C++20: The body may contain dynamic memory allocations (e.g., new/delete) and virtual function calls, provided the allocations are transient (the memory must be deallocated before the compile-time evaluation completes).
class ComplexInit {
public:
    int value;

    // C++14 and later: Logic allowed inside the body
    constexpr ComplexInit(int a, int b) : value(0) {
        if (a > b) {
            value = a * 2;
        } else {
            for (int i = 0; i < b; ++i) {
                value += a;
            }
        }
    }
};

Evaluation Mechanics

The constexpr specifier on a constructor acts as a capability, not an absolute mandate. The compiler determines the execution context based on the invocation:
  • Compile-Time Evaluation: If the constructor is invoked to initialize a constexpr variable, or used in a context requiring a constant expression (e.g., template arguments, static_assert, array bounds), the compiler mandates that all arguments are constant expressions and executes the constructor during compilation.
  • Constant Initialization: If a constexpr constructor is used to initialize an object with static or thread-local storage duration (e.g., static Point p(10.5, 20.0);), it undergoes constant initialization at compile-time only if the arguments passed to the constructor are also valid constant expressions. If the constexpr constructor is invoked with runtime arguments, the compiler silently falls back to dynamic initialization at runtime. This exact pitfall—accidentally triggering dynamic initialization and risking the Static Initialization Order Fiasco—is why the constinit keyword was introduced in C++20.
  • Runtime Evaluation: If the constructor is invoked with runtime variables for an object with automatic or dynamic storage duration, the constexpr constructor degrades gracefully to a standard runtime constructor. No duplicate runtime constructor needs to be written.

Defaulted Constexpr Constructors

A constructor can be explicitly defaulted using = default alongside the constexpr specifier.
int get_runtime_value();

class Wrapper {
public:
    constexpr Wrapper() = default;
private:
    // In C++23, this is allowed even though get_runtime_value() is not constexpr.
    // It will only cause a compilation error if the constructor is actually 
    // evaluated in a constant context. Prior to C++23, this would make the 
    // constexpr constructor declaration ill-formed.
    int data = get_runtime_value(); 
};
If the compiler generates a default constructor for a class, it will implicitly mark it constexpr if the equivalent user-written constructor would satisfy the requirements of a constexpr constructor. Specifically, the class must not have virtual base classes, and all base class and non-static data member default constructors must themselves be constexpr. A class can possess an implicitly constexpr default constructor without strictly qualifying as a literal type (for example, by having a non-trivial destructor in C++11 through C++17). Prior to C++23’s relaxation of constexpr rules, explicitly defaulting a constexpr constructor required that any default member initializers be valid constant expressions just to allow the constructor to be validly marked constexpr. Since C++23, this is no longer a strict requirement; the presence of a non-constant default member initializer will not invalidate the constexpr declaration itself, but will trigger an error only if the constructor is actually evaluated in a constant context.
Master C++ with Deep Grasping Methodology!Learn More