I have understood that std::variant would be a replacement for union.
Yes. Kind of. But your misunderstanding seems to be with unions in C++. From cppreference:
It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.
Note that this is different from C, which is one root cause of a common misunderstanding that accessing an inactive member would also be allowed in C++.
Why?
Because std::variant mimics unions but add some safety and std::get is...
Index-based value accessor: If v.index() == I, returns a reference to the value stored in v. Otherwise, throws std::bad_variant_access. The call is ill-formed if I is not a valid index in the variant.
.
But if this kind of basic stuff fails what is it good for?
Unions in C++ were never meant to be used like that. Unions are rather to save memory when you want to squeeze two or more values into the same memory (but at any time only use one of them). I never met a real use case for them. std::variant however, is a nice addition to C++ types. The bigger picture is that already since long time there were so-called product types, like std::pair<T1,T2>, in the sense that the set of values they can represent is T1 x T2. With std::variant (and std::any) now C++ also has proper (= with a degree of typesafety that you never got from unions) sum types, ie the set of values a std::variant<T1,T2> can represent is T1 + T2 (sorry for using sloppy notation, hope it is clear).
I have understood that std::variant would be a replacement for union.
Yes. Kind of. But your misunderstanding seems to be with unions in C++. From cppreference:
It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.
Note that this is different from C, which is one root cause of a common misunderstanding that accessing an inactive member would also be allowed in C++.
Why?
Because std::variant mimics unions but add some safety and std::get is...
Index-based value accessor: If v.index() == I, returns a reference to the value stored in v. Otherwise, throws std::bad_variant_access. The call is ill-formed if I is not a valid index in the variant.
.
But if this kind of basic stuff fails what is it good for?
Unions in C++ were never meant to be used like that. Unions are rather to save memory when you want to squeeze two or more values into the same memory (but at any time only use one of them). I never met a real use case for them. std::variant however, is a nice addition to C++ types. The bigger picture is that already since long time there were so-called product types, like std::pair<T1,T2>, in the sense that the set of values they can represent is T1 x T2. With std::variant (and std::any) now C++ also has proper (= with a degree of typesafety that you never got from unions) sum types, ie the set of values a std::variant<T1,T2> can represent is T1 + T2 (sorry for using sloppy notation, hope it is clear).
std::variant is a “type-safe union”, meaning it’ll check that what you get from it is the type that is last stored in it. In this example you are storing a double but trying to get an uint64_t.
See the docs here: https://en.cppreference.com/w/cpp/utility/variant/get
Type-based value accessor: If v holds the alternative T, returns a reference to the value stored in v. Otherwise, throws std::bad_variant_access. The call is ill-formed if T is not a unique element of Types....
std::visit will only trigger a bad_variant_access exception if the variant is valueless_by_exception (C++17, see N4659 23.7.3.5 [variant.status] )
What this means is that if you tried to set a variant value in a fashion that throws an exception, the variant is left in a "valueless" state, so visitation is not permitted.
To trigger it, we can change the code like so:
struct S{
operator int() const{throw 42;}
};
struct printer{//as before};
int main() {
using my_variant = std::variant<int, float, double>;
my_variant v0{'c'};
try{
v0.emplace<0>(S());
}catch(...){}
try {
std::visit(printer{}, v0);
}
catch(const std::bad_variant_access& e) {
std::cout << e.what() << '\n';
}
}
Demo
Frank already answered why you could construct your variant in the first place using a char (construction chosen via overload).
You can not trigger a bad_variant_access by attempting to first construct a variant in a fashion that will throw because [variant.ctor] dictates that the constructor will rethrow that exception (in this case int).
According to the documentation for std::variant:
That constructor of variant does the following:
Constructs a variant holding the alternative type T_j that would be selected by overload resolution for the expression F(std::forward(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types... in scope at the same time. [...]
I.e. std::variant will get the first type that is constructible from the passed argument. In this case, int is constructible from a char, so the variant gets assigned as such.