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.
> 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.
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?
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 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.
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, 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.
That aside, have you ever felt the need to implement your own Win32 event, or SDL event, or X11 event, or epoll_event, or a custom DOM event or whatever?
If you're writing a good library, give the user the data they need, and let them handle it however they wish, don't force them int your framework.
That doesn't work if your library needs to handle these events somehow, not just transparently pass them around.
Typically with your pattern, you'd use a switch statement on the event type, but what do you do if it's in the user-defined space (type>=EVENT_USER_START)? You could add mandatory function pointers for relevant handlers in a header prefix, but now you're just reimplementing virtual methods. As is common in C, you end up making your own object-oriented language, only with more effort and not as good.
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.
> This has nothing to do with what the blog post is about.
It's an alternative way to solve the problem presented, which trivially allows you to make a std::vector<Event> (the whole point the article is building up to).
> the only thing std::polymorphism brings is the ability to do it without dynamic allocations, emplacement new or lifetime problems.
> it's an alternative way to solve the problem presented
No it's not and you apparently did not understand the concept behind it (or more likely, do not want to understand the concept behind it).
Your solution is not an implementation of Open Polymorphism, it is just a poor man implementation of std::variant.
> Which the version I showed has none of.
Because it is not the same thing like explain by my previous comment and in around 5 other comments around mine soon I guess.
But you are entirely free to grunt around and continue to imply that C++ committee people are implementing something useless (Which they are not, this is useful).
My point is that you don't need open polymorphism for the example given, and if you give me another example, I can show you why you don't need it there either -- because you do in fact never need it. It's an XY problem over the set X.
If a programmer is reaching for a library, they want it to solve a concrete task. Nobody wants to subclass your Event type, if I have to do it, you've failed at your job.
No, it has something much worse, it has a memory leak. The nested types inside the union allocate memory, and they don't get destructed when Event gets destructed. Don't do this in modern C++, it's a terrible terrible idea, these kinds of constructs are very hard to get correct manually. Just use std::variant.
Typical C approach.
In C++ this should be done with std::variant instead. It has the exact same binary layout, but is type-safe (compared to union usage).
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.
> 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...
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.
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.
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.
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.
Even for a self-contained code base, it's a pain compared to a type that just works with any current or future derived class.
If you're writing a good library, give the user the data they need, and let them handle it however they wish, don't force them int your framework.
Typically with your pattern, you'd use a switch statement on the event type, but what do you do if it's in the user-defined space (type>=EVENT_USER_START)? You could add mandatory function pointers for relevant handlers in a header prefix, but now you're just reimplementing virtual methods. As is common in C, you end up making your own object-oriented language, only with more effort and not as good.
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.
It's an alternative way to solve the problem presented, which trivially allows you to make a std::vector<Event> (the whole point the article is building up to).
> the only thing std::polymorphism brings is the ability to do it without dynamic allocations, emplacement new or lifetime problems.
Which the version I showed has none of.
No it's not and you apparently did not understand the concept behind it (or more likely, do not want to understand the concept behind it).
Your solution is not an implementation of Open Polymorphism, it is just a poor man implementation of std::variant.
> Which the version I showed has none of.
Because it is not the same thing like explain by my previous comment and in around 5 other comments around mine soon I guess.
But you are entirely free to grunt around and continue to imply that C++ committee people are implementing something useless (Which they are not, this is useful).
If a programmer is reaching for a library, they want it to solve a concrete task. Nobody wants to subclass your Event type, if I have to do it, you've failed at your job.
No, it has something much worse, it has a memory leak. The nested types inside the union allocate memory, and they don't get destructed when Event gets destructed. Don't do this in modern C++, it's a terrible terrible idea, these kinds of constructs are very hard to get correct manually. Just use std::variant.
[0]: https://eel.is/c++draft/polymorphic
Not adding more stuff. So I get security, legacy compatibility and the existing flexibility.