On Mon, Apr 15, 2019 at 8:16 AM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
pon., 15 kwi 2019 o 16:46 Phil Endecott via Boost
napisaĆ(a): Emil Dotchevski wrote:
If the design allows for one more state, then that state must be
handled:
various functions in the program must check "is the object empty" and define behavior for that case.
I think that's often not really the case; at least, it is not a large burden.
It seems to me that in many/most cases, the empty state is essentially transient; it can exist between the throw in the assignment and the end of the variant's scope:
void f() { variant<...> v{42}; v = something; // throw in assignment; v is empty foo(); // skipped blah(); // skipped // v is destructed }
You can only observe the empty state if you have a try/catch inside the scope of the variant. Or possibly something with a dtor that accesses the variant. If you limit yourself to not doing that, then you can ignore the possibility of empty in the rest of your logic.
What do others think? Do you believe that it would be common to catch the exception thrown during the variant assignment and not "fix up" the variant's value, such that code after the catch could see the variant in its empty state?
"Common" may not be the right word here. If there were practical use cases in correct programs that do it that are not common we would have to strive even more to address this case. But my position is that programs that correctly handle exceptions, and where people understand what a basic guarantee is and is not, *never* do this.
From cppreference: "Basic exception guarantee -- If the function throws an exception, the program is in a valid state. It may require cleanup, but all invariants are intact."
"All invariants are intact": f.e. even after std::vector::op= fails, the target vector is guaranteed to be in a perfectly valid state. By analogy, the "valueless by exception" state in variant must be a valid state, which means that various operations may not result in UB even after assignment failure.