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.
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"
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.
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.
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
lcapaldo | 10 hours ago
Does it “just work”? Given
seems like you really need an empty state to not have an infinite recursive construction in this example.
hjvt | 10 hours ago
I would hope that
std::optional<std::indirect<Node>>works as expected to add the empty state back inmuvlon | 7 hours ago
They do concede that there is an empty moved-from state in
std::indirecttoo, but I don't know whether they're saying it's good to abuse this here to get an empty state forNode. Maybe they just forgot the optional here?lcapaldo | 6 hours ago
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.
muvlon | 2 hours ago
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.
hmry | 6 hours ago
Does
std::optional<std::indirect<T>>have a specialization to optimize to be the same size asstd::unique_ptr<T>? Or will it be a bool + a pointer?aw1621107 | 4 hours ago
According to the proposal a
std::optionalspecialization 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:
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.
hmry | 3 hours ago
That's very unfortunate.
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).
muvlon | 2 hours ago
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 asoptional<T>, but you only expose&Tin public, not the optionals. Now somebody could break your invariants, leading to incorrect bookkeeping and potentially data corruption.aw1621107 | an hour ago
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 forstd::nulloptand evaluate totruefor an engaged-but-valueless state, but if you collapse the two states what should the code do?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.
gignico | 7 hours ago
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.
dzwdz | 3 hours ago
Is this something you could implement yourself pre-C++26, or do you need special compiler support to achieve these semantics?
gignico | 33 minutes ago
This could have been implemented even in C++11 as far as I know
lcapaldo | 2 hours ago
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