C++26: Constexpr Virtual Inheritance

jandeboevrie1 pts0 comments

C++26: constexpr virtual inheritance | Sandor Dargo's Blog<br>Sandor Dargo's Blog<br>On C++, software development and books

HOME TAGS ARCHIVES BOOKS SPEAKING DAILY C++ WORKSHOPS HI... SUBSCRIBE

Blog 2026 07 01 C++26: constexpr virtual inheritance Post<br>Cancel

C++26: constexpr virtual inheritance<br>Sandor Dargo Jul 1 2026-07-01T00:00:00+02:00<br>4 min

constexpr has come a long way since its introduction in C++11. Back then, constexpr functions were extremely restricted and could essentially contain only a single return statement. C++14 relaxed those restrictions, allowing local variables, loops, and multiple statements. C++20 was another major milestone, bringing support for constexpr dynamic allocation, enabling types such as std::string and std::vector to become usable in constant evaluation, and allowing constexpr virtual functions. C++23 further relaxed the rules by permitting static local constexpr variables and try blocks inside constexpr functions. C++26 continues the trend, adding support for exception handling during constant evaluation and several other improvements in the language and in the library we’ve already covered on this blog.<br>But somehow P3533R2 by Hana Dusíková slipped under my radar - something I realized during her excellent talk on the state of constexpr at ACCU On Sea. This paper removes one of the last remaining syntactical restrictions on constexpr: the prohibition of virtual inheritance.<br>A quick recap on virtual inheritance<br>I’ve written about virtual inheritance before, so I’ll keep this brief.<br>Virtual inheritance solves the diamond problem. Consider a Person base class with Student and Worker both inheriting from it. If TeachingAssistant inherits from both Student and Worker, it ends up with two copies of Person — and any call to a Person member becomes ambiguous:<br>10<br>11<br>12<br>struct Person {<br>virtual ~Person() = default;<br>virtual void speak() {}<br>};

struct Student: Person {};<br>struct Worker: Person {};

struct TeachingAssistant: Student, Worker {};

TeachingAssistant ta;<br>ta.speak(); // error: ambiguous

Virtual inheritance tells the compiler to keep only one shared instance of the base class, Person:<br>struct Student: virtual Person {};<br>struct Worker: virtual Person {};

struct TeachingAssistant: Student, Worker {};

TeachingAssistant ta;<br>ta.speak(); // OK — only one Person subobject

The cost is a slightly larger object (extra vtable pointers) and more complex construction semantics — the most derived class is responsible for initializing the virtual base. That’s why you should only use it when you actually need it. For the full picture, check out the earlier article.<br>What the proposal changes<br>Before P3533R2, the standard explicitly forbade constructors and destructors of types with virtual bases from being constexpr. In fact, Clang went even further and rejected all member functions of such types as constexpr, not just constructors and destructors.<br>This meant code like the following was ill-formed:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>struct Common {<br>unsigned counter{0};<br>};

struct Left : virtual Common {<br>unsigned value{0};<br>constexpr const unsigned& get_counter() const {<br>return Common::counter;<br>};

struct Right : virtual Common {<br>unsigned value{0};<br>constexpr const unsigned& get_counter() const {<br>return Common::counter;<br>};

struct Child : Left, Right {<br>unsigned x{0};<br>unsigned y{0};<br>};

constexpr auto ch = Child{}; // error before C++26<br>static_assert(&ch.Left::get_counter() == &ch.Right::get_counter());

With P3533R2 accepted into C++26, this code just works. You can construct objects with virtual bases at compile time, and constexpr member functions in such hierarchies are perfectly fine.<br>Why this matters<br>You might think: virtual inheritance is rare enough at runtime, who needs it at compile time? The answer is the standard library.<br>std::ios_base — the foundation of the entire iostream hierarchy — uses virtual inheritance. That single fact has blocked the constexpr-ification of streams. And streams being non-constexpr has cascading effects. For example, ’s exception types chrono::nonexistent_local_time and chrono::ambiguous_local_time need stream formatting. Making basic_istream_view usable in constexpr range-based parsing also depends on this.<br>By lifting this restriction, P3533R2 unblocks a whole chain of library improvements.<br>There’s a deeper point, too. Before this paper, the standard defined a concept called constexpr-suitable: a function was constexpr-suitable if it wasn’t a coroutine and its class had no virtual bases. With virtual inheritance now allowed, the only remaining restriction is coroutines (which P3367 is addressing for C++29). Once coroutines are handled too, every function will be constexpr-suitable and the term becomes meaningless.<br>In other words, constexpr is moving toward being a simple opt-in rather than a keyword guarded by a list of syntactical restrictions. The language is converging on a model where only the evaluation properties of code — defined in...

constexpr virtual inheritance person struct unsigned

Related Articles