C++26: std::indirect
8 points by raymii
8 points by raymii
Recursive types: a struct Node { int value; std::indirect<Node> next; }; just works
Does it “just work”? Given
indirect has no null or empty state by design.
seems like you really need an empty state to not have an infinite recursive construction in this example.
I would hope that std::optional<std::indirect<Node>> works as expected to add the empty state back in
They do concede that there is an empty moved-from state in std::indirect too, but I don't know whether they're saying it's good to abuse this here to get an empty state for Node. Maybe they just forgot the optional here?
I don’t think you can abuse the moved from state for this as written, the default constructor is going to construct a Node. You’d have to write more code to get a moved from one to work with. Maybe they did forget the optional, but whether abuse or forgetting something “just works” seems to be overselling it imo.
Yeah, I just tried this for a bit but couldn't even find a UB-free way to get a moved-from std::indirect<Node>. It definitely doesn't just work.
Does std::optional<std::indirect<T>> have a specialization to optimize to be the same size as std::unique_ptr<T>? Or will it be a bool + a pointer?
According to the proposal a std::optional specialization was originally proposed but was removed in revision 3. The paper states that such a specialization would "Break[] ABI; engaged but valueless optional would become indistinguishable from a disengaged optional"
The corresponding GitHub issue has a little bit more:
POLL: Approve design of P3019R2 (Vocabulary Types for Composite Class Design), except ensure the swap noexcept specification matches std::vector and remove the std::optional specialization.
[ SF: 10; WF: 12; N: 2; WA: 2; SA: 0]
WA: Can't vote in favor of the approach that makes the valueless state unsafe
Unfortunately more detailed meeting notes aren't public so more info would probably have to come from those that were more involved with the process.
That's very unfortunate.
engaged but valueless optional would become indistinguishable from a disengaged optional
I don't see why that's a problem. I thought the only things you were supposed to do with valueless values are destroy them or replace them using an assignment constructor. An empty optional and moved-from indirect are both safe to destruct by no-op. Why do you need the moved-from state to remember if it was engaged or disengaged before the move? Or am I missing something? (Or misunderstanding what they mean by engaged but valueless?)
Sad seeing C++ moving away from zero-cost abstractions because of ABI stability and valueless-after-move (which is shaping up to be a major legacy cruft item that infects the entire standard library).
Why do you need the moved-from state to remember if it was engaged or disengaged before the move?
I think the crux is that you can't know which user code needs this. And you have to keep in mind that users might be writing code for generic optional<T>, expecting that there is no way for that to become disengaged simply my mutating the contained T in an engaged optional. For example, you might be writing a generic container class that stores items in slots as optional<T>, but you only expose &T in public, not the optionals. Now somebody could break your invariants, leading to incorrect bookkeeping and potentially data corruption.
If you can't distinguish between a disengaged optional and an engaged-but-valueless std::optional<std::indirect<T>> then I think you make otherwise well-defined code ambiguous. For instance, opt.value().valueless_after_move() should throw for std::nullopt and evaluate to true for an engaged-but-valueless state, but if you collapse the two states what should the code do?
and valueless-after-move (which is shaping up to be a major legacy cruft item that infects the entire standard library).
Unfortunately destructive moves were deemed to be too hard to be worth the effort pre-C++11, and I'm not aware of any better solutions since then that would have fit neatly into the pre-C++11 object model.
I remember talking about the necessity of this type with colleagues just after C++11 came out. So it’s a welcome addition for sure. At least for those who can use C++26 already.
Is this something you could implement yourself pre-C++26, or do you need special compiler support to achieve these semantics?
It’s implementable pre c++26 for example https://github.com/jbcoe/value_types
There are a few others floating around. When I saw this for the first time I was retrospectively shocked that we didn’t have something like it at $DAYJOB