Is GCC9 avoiding valueless state of std::variant allowed?
I think the important part of the standard is this:
From https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12
23.7.3.4 Modifiers
(...)
template variant_alternative_t>& emplace(Args&&... args);
(...) If an exception is thrown during the initialization of the contained value, the variant might not hold a value
It says "might" not "must". I would expect this to be intentional in order to allow implementations like the one used by gcc.
As you mentioned yourself, this is only possible if the destructors of all alternatives are trivial and thus unobservable because destroying the previous value is required.
Followup question:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std::forward<Args>(args)....
Does T tmp {std::forward(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
Yes, because for types that are trivially copyable there is no way to detect the difference, so the implementation behaves as if the value was initialized as described. This would not work if the type was not trivially copyable.
So does the standard indeed allow, that
emplace
does not change the current value?
Yes. emplace
shall provide the basic guarantee of no leaking (i.e., respecting object lifetime when construction and destruction produce observable side effects), but when possible, it is allowed to provide the strong guarantee (i.e., the original state is kept when an operation fails).
variant
is required to behave similarly to a union — the alternatives are allocated in one region of suitably allocated storage. It is not allowed to allocate dynamic memory. Therefore, a type-changing emplace
has no way to keep the original object without calling an additional move constructor — it has to destroy it and construct the new object in place of it. If this construction fails, then the variant has to go to the exceptional valueless state. This prevents weird things like destroying a nonexistent object.
However, for small trivially copyable types, it is possible to provide the strong guarantee without too much overhead (even a performance boost for avoiding a check, in this case). Therefore, the implementation does it. This is standard-conforming: the implementation still provides the basic guarantee as required by the standard, just in a more user-friendly way.
Edit in response to a standard quote:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments
std::forward<Args>(args)...
.Does
T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
really count as a valid implementation of the above? Is this what is meant by "as if"?
Yes, if the move assignment produces no observable effect, which is the case for trivially copyable types.