pon., 4 mar 2019 o 00:54 Gavin Lambert via Boost
On 3/03/2019 07:25, Niall Douglas wrote:
I just don't get why variant2 would set a state of C when it had state A, and setting state B failed. It should have spotted the lack of common noexcept move, employed double buffers, and alternated between them such that state A is untouched should setting state B fail.
If it's going to do weirdness like setting state C out of the blue, then better dispose entirely any double buffered implementation as not adding value.
While I agree that switching to an apparently unrelated state is surprising, it could be anticipated and "solved" by always using monostate as the first type where the types might throw on move. (And perhaps variant could issue a warning or error if you try to do otherwise?)
It seems reasonable to require a monostate state to exist unless using types that can guarantee that it is never needed. And with it "in their face" in the type declaration, consumers of the variant should be less likely to forget about handling it, which is one of the problems with valueless_by_exception.
I find it a lot more common that people writing move constructors/assignment forget to declare it noexcept than it actually being exception-prone, so having more diagnostics ("if you really meant to do that, add monostate") rather than silently degrading performance seems like a good thing.
To summarize a bit, there are four mechanisms for assuring that implicit "valueless" state never occurs: 1. Just assume that operations involved never throw (this works for some types) 2. Make the "valueless" state explicit by using monostate. 3. Apply some tricks with move constructors or default constructors to bring back *any* state other than "valueless". 4. Use double buffering variant2 tries to cleverly select the best approach for a given set of types. While it is obvious that option 4 comes with the cost, it is worth noting that option 3 is also not free. Using option 2 changes the contract to the extent that you are in fact creating a different type with different invariant. This might be a good default for some applications, but programmers often want to make this decision consciously and explicitly. (On a peripherally related note, why did variant introduce monostate
instead of reusing nullopt_t?)
nullopt_t was a compromise: the Committee didn't feel comfortable with introducing a generic boost::none_t with this name with well defined semantics (comparability, ordering, interaciotns with nullptr_t) that soon in the process, so we provided something that was supposed to be a temporary solution, a tag (like std::piecewise_construct) whose only purpose is to indicate an intention to initialize std::optional to a state of not containing a value. Now std::monostate has taken the role of boost::none_t. It would make sense to use it in std::optional: std::optional<int> oi = std::monostate{}; Regards, Andrzej