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 - #include <iostream> #include <variant> int main() { std::variant<int, float> v; v = 12; try { std::get<float>(v); } catch (const std::bad_variant_access& e) { std::cout << e.what() << '\n'; } }
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....

🌐
SACO Evaluator
saco-evaluator.org.za › docs › cppreference › en › cpp › utility › variant › bad_variant_access.html
std::bad_variant_access - cppreference.com
#include <variant> #include <iostream> int main() { std::variant<int, float> v; v = 12; try { std::get<float>(v); } catch(const std::bad_variant_access& e) { std::cout << e.what() << '\n'; } }
🌐
Cppreference
en.cppreference.com › w › cpp › utility › variant.html
std::variant - cppreference.com
February 19, 2025 - #include <cassert> #include <iostream> #include <string> #include <variant> int main() { std::variant<int, float> v, w; v = 42; // v contains int int i = std::get<int>(v); assert(42 == i); // succeeds w = std::get<int>(v); w = std::get<0>(v); // same effect as the previous line w = v; // same effect as the previous line // std::get<double>(v); // error: no double in [int, float] // std::get<3>(v); // error: valid index 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 names
🌐
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.
🌐
Basicexamples
basicexamples.com › example › cpp › std-bad-variant-access
Basic example of std::bad_variant_access in C++
#include <iostream> #include <variant> int main() { std::variant<int, double, std::string> myVariant = "Hello"; try { std::cout << std::get<int>(myVariant) << std::endl; } catch (const std::bad_variant_access& ex) { std::cerr << "Exception: " << ex.what() << std::endl; } return 0; } In this example, a variant object myVariant is declared with available types int, double, and std::string.
🌐
Cppreference
en.cppreference.com › w › cpp › utility › variant › get
std::get(std::variant) - cppreference.com
August 22, 2024 - #include <iostream> #include <string> #include <variant> int main() { std::variant<int, float> v{12}, w; std::cout << std::get<int>(v) << '\n'; w = std::get<int>(v); w = std::get<0>(v); // same effect as the previous line // std::get<double>(v); // error: no double in [int, float] // std::get<3>(v); // error: valid index values are 0 and 1 try { w = 42.0f; std::cout << std::get<float>(w) << '\n'; // ok, prints 42 w = 42; std::cout << std::get<float>(w) << '\n'; // throws } catch (std::bad_variant_access const& ex) { std::cout << ex.what() << ": w contained int, not float\n"; } }
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.

🌐
W3cubDocs
docs.w3cub.com › cpp › utility › variant › bad_variant_access
Std::bad_variant_access - C++ - W3cubDocs
#include <variant> #include <iostream> int main() { std::variant<int, float> v; v = 12; try { std::get<float>(v); } catch(const std::bad_variant_access& e) { std::cout << e.what() << '\n'; } }
Find elsewhere
🌐
MC++ BLOG
modernescpp.com › index.php › visiting-a-std-variant-with-the-overload-pattern
Visiting a std::variant with the Overload Pattern – MC++ BLOG
September 23, 2021 - Here is an example based on cppreference.com. // variant.cpp #include <variant> #include <string> int main(){ std::variant<int, float> v, w; v = 12; // (1) int i = std::get<int>(v); w = std::get<int>(v); // (2) w = std::get<0>(v); // (3) w = v; // (4) // std::get<double>(v); // (5) ERROR // std::get<3>(v); // (6) ERROR try{ std::get<float>(w); // (7) } catch (std::bad_variant_access&) {} std::variant<std::string> v("abc"); // (8) v = "def"; // (9) }
🌐
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 - When processing scan results in PropertiesChanged signal handler (which has signature sa{sv}as), we are supposed to read() into three variables, the first one being a string, second a map and the third one a vector<string>. Based on example code and on debug output from the python scripts doing the same thing, I made the reading method as following: // ... typedef map<int16_t, variant<vector<uint8_t>>> manufacturer_data_t; map<string, std::variant<int16_t, string, manufacturer_data_t>> changedProperties; vector<string> invalidatedProperties; string interface; msg.read(interface, changedProperties, invalidatedProperties); // ... However, I encounter an std::bad_variant_access exception when trying to access variants in this map.
🌐
Draft C++ Standard
eel.is › c++draft › variant.bad.access
[variant.bad.access]
June 22, 2011 - namespace std { class bad_variant_access : public exception { public: // see [exception] for the specification of the special member functions constexpr const char* what() const noexcept override; }; }
🌐
Simplify C++!
arne-mertz.de › home › modern c++ features – std::variant and std::visit
Modern C++ Features - std::variant and std::visit - Simplify C++!
June 5, 2018 - So, in the little example above, the index of io is 0 after the construction, because std::vector<int> is the first type in the list. After the assignment with the double, the index is 1. The currently active index can be obtained by the member function variant::index(). If we know the index at compile time, we can get the value stored in the variant using std::get<I>. std::get will throw an exception of type std::bad_variant_access if I is not the active index.
🌐
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.
🌐
Google Groups
groups.google.com › a › isocpp.org › g › std-proposals › c › DxvEBfamvZ0 › m › 6u_IR8LJAQAJ
Why std::optional has checked and unchecked access, but std::variant only checked?
In std::optional we have: - operator* — doesn't check whether an optional contains a value; - method value — throws std::bad_optional_access when an optional doesn't contain a value. In std::vector we have: - operator[] — doesn't check whether a vector contains specified index; - method at — throws std::out_of_range when a vector doesn't contain specified index. In std::variant we have: - ???
🌐
Medium
medium.com › @weidagang › modern-c-std-variant-and-std-visit-3c16084db7dc
Modern C++: std::variant and std::visit | by Dagang Wei | Medium
August 27, 2024 - Example: #include <iostream> #include <variant> #include <string> int main() { std::variant<int, std::string> data; data = 42; std::cout << std::get<int>(data) << std::endl; // Output: 42 (Correct access) data = "Hello, world!"; // Trying to access it as an int (INCORRECT) // std::cout << std::get<int>(data) << std::endl; // This will throw a std::bad_variant_access exception at runtime std::cout << std::get<std::string>(data) << std::endl; // Output: Hello, world!
🌐
Corecppil
corecppil.github.io › Meetups › 2018-05-28_Practical-C++Asio-Variant › Variants.pdf pdf
A variety of variants Dvir Yitzchaki
catch (const std::bad_variant_access &e) { std::cout << e.what() << '\n'; // prints 'bad variant access' } // std::cout << std::get<long>(v) << '\n'; // doesn't compile · v = "forty two"; std::cout << std::get<std::string>(v) << '\n'; // prints 'forty two' 37 ·