The Compiler Discrepancy: Clang vs. GCC & MSVC

When working with the Curiously Recurring Template Pattern (CRTP) in modern C++, you might occasionally run into cases where compilers disagree on code validity. A classic example of this is attempting to modify a derived class member from a base class constructor during constexpr evaluation.

Consider the following minimal reproducible example:

template<typename D>
struct Base {
    constexpr Base(int x) {
        static_cast<D*>(this)->value = x;
    }
};

struct Derived : public Base<Derived> {
    int value;
    using Base<Derived>::Base;
};

constexpr Derived d(42);

int main() {}

If you compile this code, GCC and MSVC accept it without any complaints. However, Clang rejects it with an error similar to:

error: constexpr variable 'd' must be initialized by a constant expression
note: write to member 'value' of lifetime-ended object is not allowed in a constant expression

So, who is right? Is this valid C++ standard behavior, or is Clang being overly pedantic?

Why Clang is Correct: Understanding Object Lifetime

According to the C++ standard, Clang is 100% correct to reject this code. The issue lies in the rules governing object lifetime during construction, specifically when evaluated in a constexpr context.

When you instantiate Derived d(42), the construction happens in a strict order:

  1. First, the base class Base<Derived> is constructed.
  2. Next, the members of the derived class (like Derived::value) are constructed.
  3. Finally, the body of the Derived constructor (if any) executes.

During step 1, while the Base<Derived> constructor is executing, the Derived portion of the object has not yet begun its lifetime. Inside the base constructor, attempting to cast this to D* and write to value means you are modifying a member of an object that does not yet exist.

In standard runtime C++, modifying a derived member from a base constructor is technically Undefined Behavior (UB). At runtime, compilers often optimize this in a way that "just works" because the memory addresses line up. However, in a constexpr (constant evaluation) context, the C++ standard strictly prohibits any form of Undefined Behavior. Because Clang’s constexpr evaluator strictly tracks object lifetimes, it correctly flags this UB as a compilation error.

How to Fix the Issue

To make your code standard-compliant and compatible across all major compilers, you must avoid writing to derived members before the derived object's lifetime begins. Here are the best ways to refactor this pattern.

Solution 1: Use a Dedicated Derived Constructor

Instead of inheriting the base constructor and forcing the base class to initialize derived members, define a constructor in the Derived class that properly initializes its own members and passes necessary data up to the base class (if needed):

template<typename D>
struct Base {
    constexpr Base() = default;
};

struct Derived : public Base<Derived> {
    int value;
    
    // Properly initialize value using member initializer list
    constexpr Derived(int x) : Base<Derived>(), value(x) {}
};

Solution 2: Passing Values Up to the Base Class

If the base class actually needs to hold or manage the value, the value should be stored as a member of the Base class rather than the Derived class:

template<typename D>
struct Base {
    int value;
    constexpr Base(int x) : value(x) {}
};

struct Derived : public Base<Derived> {
    using Base<Derived>::Base; // Inheriting constructor is now perfectly valid
};

Summary

While GCC and MSVC are lenient with this specific CRTP pattern, Clang correctly enforces the C++ standard. Accessing or writing to derived members during base class construction is undefined behavior, making it illegal in a constexpr context. By structuring your constructors to respect the natural order of object lifetime initialization, you ensure your code remains portable, standard-compliant, and future-proof.