6

this is example inspired by example from cppreference

struct S {
    operator int() { throw 42; }
};


int main(){
    variant<float, int> v{12.f}; // OK
    cout << std::boolalpha << v.valueless_by_exception() << "\n";
    try{
        v.emplace<1>(S()); // v may be valueless
    }
    catch(...){
    }
    cout << std::boolalpha << v.valueless_by_exception() << "\n";   
}

For one compiler I tried it outputs

false, true

meaning that emplace caused the variant to become valueless

What I do not understand is how this happened. In particular I do not understand why emplace is called at all, I would expect the program to not even call it since conversion from S to int argument throws.

Quentin
  • 62,093
  • 7
  • 131
  • 191
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277

1 Answers1

8

Note the signature for the relevant std::variant::emplace overload:

template <size_t I, class... Args>
std::variant_alternative_t<I, variant>& emplace(Args&&... args);

It takes a pack of forwarding references. This means that the conversion operator from S to int is not called when evaluating the function arguments; it's called inside the body of emplace. Since trying to construct the int in place would then fail, the variant is made valueless by exception.

It could perhaps be possible to implement variant such that for trivially movable types, the old value is saved before in place construction is attempted, and then restored if it failed, but I'm not sure if it fits in with the various restrictions on the type's implementation given by the standard.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 1
    Regarding design decisions here is a nice link for this https://cbeck88.github.io/strict-variant/strict_variant/overview/design.html – NoSenseEtAl Nov 24 '17 at 10:40
  • "I'm not sure if it fits in with the various restrictions on the type's implementation given by the standard." I've heard this explanation cited on the Lib reflectors to explain why the `emplace` wording says that the variant "*might* not hold a value" if the initialization throws, but I have a hard time reading "Destroys the currently contained value if `valueless_by_exception()` is `false`." as "makes a copy of the currently contained value to later reinsert into the `variant` on exception." – Casey Nov 24 '17 at 15:49
  • 2
    Boost has the never empty guarantee… in this case, I believe `boost::variant` saves the old value of `float` because it can be nothrow-copied/moved back. In the rare case that there is no way to restore any value as `noexcept`, they use a heap allocation and "double buffering". `valueless_by_exception` was chosen for `std::variant` instead of the never-empty guarantee for performance reasons and because `boost::variant`'s failure modes can be very surprising. – Arne Vogel Nov 25 '17 at 06:06