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).

Answer from 463035818_is_not_an_ai on Stack Overflow
🌐
Cppreference
en.cppreference.com › w › cpp › utility › variant › bad_variant_access
std::bad_variant_access - cppreference.com
December 12, 2023 - Assigns the contents with those of other. If *this and other both have dynamic type std::bad_variant_access then std::strcmp(what(), other.what()) == 0 after assignment.
Top answer
1 of 2
8

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).

2 of 2
1

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....

🌐
Cppreference
en.cppreference.com › w › cpp › utility › variant › get
std::get(std::variant) - cppreference.com
August 22, 2024 - The call is ill-formed if I is not a valid index in the variant. 2) 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.
🌐
Timsong-cpp
timsong-cpp.github.io › cppwp › n4659 › variant.bad.access
[variant.bad.access]
Objects of type bad_­variant_­access are thrown to report invalid accesses to the value of a variant object.
Top answer
1 of 2
3

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).

2 of 2
2

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.

🌐
GitHub
github.com › mapbox › variant › issues › 95
warning: 'bad_variant_access' · Issue #95 · mapbox/variant
deps/mapbox/variant/variant.hpp:58:7: warning: 'bad_variant_access' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables] class bad_variant_access : public std::runtime_error ^
🌐
Draft C++ Standard
eel.is › c++draft › variant.bad.access
[variant.bad.access]
June 22, 2011 - Objects of type bad_variant_access are thrown to report invalid accesses to the value of a variant object.
Find elsewhere
🌐
Microsoft Learn
learn.microsoft.com › en-us › cpp › standard-library › bad-variant-access-class
bad_variant_access Class | Microsoft Learn
August 3, 2021 - Access to this page requires authorization. You can try changing directories. ... Objects of type bad_variant_access are thrown to report invalid accesses to the value of a variant object.
🌐
W3cubDocs
docs.w3cub.com › cpp › utility › variant › bad_variant_access
Std::bad_variant_access - C++ - W3cubDocs
Assigns the contents with those of other. If *this and other both have dynamic type std::bad_variant_access then std::strcmp(what(), other.what()) == 0 after assignment.
🌐
Cppreference
en.cppreference.com › w › cpp › utility › variant.html
std::variant - cppreference.com
February 19, 2025 - #include <cassert> #include <iostream> ... values are 0 and 1 try { std::get<float>(w); // w contains int, not float: will throw } catch (const std::bad_variant_access& ex) { std::cout << ex.what() << '\n'; } using namespace std::literals; std::variant<std::string> x("abc"); // ...
🌐
GitHub
github.com › openbmc › bios-settings-mgr › issues › 2
terminate called after throwing an instance of 'std::bad_variant_access' · Issue #2 · openbmc/bios-settings-mgr
September 14, 2021 - Sep 14 13:30:34 p10bmc biosconfig-manager[309]: terminate called after throwing an instance of 'std::bad_variant_access' Sep 14 13:30:34 p10bmc biosconfig-manager[309]: what(): std::get: wrong index for variant Sep 14 13:30:34 p10bmc systemd-coredump[743]: Process 309 (biosconfig-mana) of user 0 dumped core.
Author   geissonator
🌐
GitHub
github.com › nwjs › nw.js › issues › 7652
Payload.exe bad variant access · Issue #7652 · nwjs/nw.js
January 10, 2021 - NWJS Version : 0.50.2 SDK Operating System : Windows 10 Enterprise (x64, 20H2) Expected behavior payload.exe should produce payload.json Actual behavior Error: [bad_variant_access.cc : 44] RAW: Bad variant access How to reproduce I insta...
Author   OsmanAltun
🌐
C++ Stories
cppstories.com › 2018 › 06 › variant
Everything You Need to Know About std::variant from C++17 - C++ Stories
January 23, 2023 - Also note that a variant that is “valueless by exception” is in an invalid state. Accessing a value from such variant is not possible. That’s why variant::index returns variant_npos, and std::get and std::visit will throw bad_variant_access.
🌐
GitHub
github.com › openbmc › sdbusplus › issues › 59
(Question) Unpacking variants in a signal handler, std::bad_variant_access · Issue #59 · openbmc/sdbusplus
March 24, 2021 - However, I encounter an std::bad_variant_access exception when trying to access variants in this map. Variant's index() would always return 0 which corresponds to int16_t in our case.
🌐
Apiref
apiref.com › cpp › cpp › utility › variant › bad_variant_access.html
std::bad_variant_access - C++ - API Reference Document
std::get(std::variant) called with an index or type that does not match the currently active alternative
🌐
cpprefjp
cpprefjp.github.io › reference › variant › bad_variant_access.html
bad_variant_access - cpprefjp C++日本語リファレンス
July 8, 2025 - std::bad_variant_accessは、std::variantオブジェクトが現在保持していない候補型に不正アクセスした際に発生する例外型である。