24 comments

[ 0.22 ms ] story [ 10.5 ms ] thread
15 years ago I would've been happy reading about all these cool new things coming to C++, nowadays I'm happy I don't use it anymore and don't need to relearn everything again. Am I getting old?
(comment deleted)
I follow you, unfortunately given that most Java and .NET implementations aren't bootstraped, there is Node.js, most modern runtimes build on top of LLVM and GCC as their backends, I still have to keep my C++ knowledge relatively of up to date.
I just don't feel this "need" to sit down and relearn anything. I become aware of new stuff through posts like this and view it as a new tool that I could but don't have to use. And if I encounter it in some code base then it's simple enough that a quick one minute look at cppreference clears any confusion.
I need default security with a #secure and #insescure.

Not adding more stuff. So I get security, legacy compatibility and the existing flexibility.

We've been doing this for 50 years, you don't need a class hierarchy for your event struct.

  enum EventType {
      EVENT_NONE,
      EVENT_TEMPERATURE_READING,
      EVENT_SMOKE_DETECTED,
      EVENT_CARBON_MONOXIDE_DETECTED,
  };
  struct Event {
      EventType type;
      union {
          TemperatureDeadingData temperature_reading;
          SmokeDetectedData smoke_detected;
          CarbonMonoxideDetected carbon_monoxide_detected
      } data;
  };

  std::vector<Event> events;
What you wrote there is just an unsafe way to do std::variant. This has nothing to do with what the blog post is about.

The blog is about Open polymorphism. Where the list of compatible Object can be extended dynamically (Which is handy for some type of error report for instance).

You can do that since forever in C++ (Since the first support for inheritance), the only thing std::polymorphism brings is the ability to do it without dynamic allocations, emplacement new or lifetime problems.

When this piece of code exists in a library, it makes it pretty much impossible to extend by the user of the library.
Yes, though in modern C++, don't do this, use std::variant instead. It has better type-safety (nothing stops you from writing to one member of the union but reading from another, causing UB) and handles lifetimes much better. Your version leaks, because struct Event wont destruct any of the nested types inside the union, and in the example, they have heap-allocated data (std::strings and the like). In order to that properly with a tagged union, you need to define the destructor of Event yourself (which is not done in your example) and call the correct destructor manually of the active union member.

Tagged unions (especially with resource-managing types) is hard to implement correctly in C++ (and not trivial in C either). Don't do it, use std::variant.

> We've been doing this for 50 years

Code that turns into an unmaintainable mess the moment even one of the types involved contains dynamically allocated data? Yes, seen that thousands of times.

The article creates a type that has typesafety, resource management and basic copy/assignment operations backed in.

Yes, the article covers this (mentions the std::variant). This makes it a closed set though. Other differences is that your Event here is as big your biggest event type, and it doesn't go through normal inheritance mechanisms. With polymorphic you could just have an abstract Event and implement your subclasses normally.

A way to see it is some sort of type-erasure over destruction and copy-construction (plus another corner case for mismatched allocators).

The constructor of polymorphics captures the type of the subclass during its constructor (they didn't make the same mistake as with shared_ptr of giving it a pointer constructor, like the UB in case the destructor is not virtual in Base *b; shared_ptr<Base> sp{b}). With this, it can instantiate a template (a control block) where the subclass is known.

Does std::polymorphic require passed types to be copyable?
Looks like it depends on the specific operation(s) you want to perform [0]. For instance, constructing a std::polymorphic<T> from an object of type T or derived from T requires that that object is copy-constructible (among other requirements), but copy-assigning one std::polymorphic<T> to another only requires that T is complete.

[0]: https://eel.is/c++draft/polymorphic

Well, https://eel.is/c++draft/polymorphic#assign-2.2 requires copy-constructibility.
Ah, you're right, that's a bad example. I had assumed that that kind of precondition would be explicitly mentioned so I didn't read the rest of the text as closely as I should have.

Perhaps move-assignment would have been a better example? That one at least mentions construction using an rvalue so I think that specific operation would work for move-only types.

Though now that I think about a bit more I suppose it's a bit of a moot point since the constructors all look like they require a copy-constructible T

Yes, there is currently no move-only std::polymorphic, like there is a move-only std::indirect ...
I followed the link near the start to the article about std::indirect<T>, which you can use instead of std::unique_ptr<T> for the pImpl idiom: [1]

> Never null: it always holds a value, except in the moved-from state (but features a member function called valueless_after_move() that lets you check the state)

Ugh, what a mess. Surely the whole point of using this rather than unique_ptr is to avoid such a trap! A safe move constructor isn't possible here because you'd either have to re-init your moved-from object, or do element-wise move (with `MyClass(MyClass&& other): impl(std::in_place, *other.impl) {}`) but those would stop your move constructor being noexcept. It would've been much more useful to make the move constructor of indirect be deleted, which is what people typically did with a pImpl-based classes in the past.

[1] https://mariusbancila.ro/blog/2026/07/23/the-pimpl-idiom-and...

> It would've been much more useful to make the move constructor of indirect be deleted

No, that would be less useful. As-is, you can use a class with a `std::indirect<T>` member in a `std::vector` just fine, thanks to the move constructor.

With `std::indirect` in `std::vector`, the vector can use move the `indirect` internally when reallocating, and as a user of the vector you will never encounter the moved-from state.

It's annoying that C++ doesn't have destructive move and thus needs to relax class invariants after a move happened, but in practice it's usually not a big deal because use-after-move is usually a bug, nothing anyone would do intentionally.

I agree. The comment is correct in that it sort-of violates the "purity" of std::indirect that it has this pathological in moved-from state, but it's only an issue when directly using moved-from objects, which is something you essentially never do. Exactly what you say, "in practice" this never happens. Throwing the move constructor out because of it would be throwing out the baby with the bathwater.

std::variant has a similar issue with valueless_after_exception, which given the semantics of the language, it has to have. But similarly, this essentially never happens in practice, and I've never encountered a bug with a valueless std::variant.

For std::variant, are you sure that it is similar? I have heard of alternatives to std::variant that avoids needing the valueless_after_exception, like what Sean Baxter proposed in a library or something, and Boost might also have something like it.
(comment deleted)
It is similar in that the valueless state isn't absolutely necessary, but avoiding the valueless state in general is not free and it seems the committee was unwilling to force implementations to incur that cost.

The docs for Boost.Variant state that avoiding a valueless state requires some kind of additional storage beyond that strictly required for the intended value. In the current implementation this means that assignment may allocate (modulo optimizations described further down the page) [0], which is obviously unacceptable for some use cases.

The proposal for std::indirect and std::polymorphic describes similar costs for avoiding a valueless state, though for moves instead of assignment [1]:

> Without a valueless state, moving indirect or polymorphic would require allocation and moving from the owned object. This would be expensive and would require the owned object to be moveable. The existence of a valueless state allows move to be implemented cheaply without requiring the owned object to be moveable.

Presumably the committee made a similar decision with respect to a valueless state for std::variant as they did for std::polymorphic/indirect.

[0]: https://www.boost.org/doc/libs/latest/doc/html/variant/desig...

[1]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p30...