I have some var = std::variant<std::monostate, a, b, c>
when a, b, c
is some types.
How, at runtime, do I check what type var
contains?
In the official documentation I found information that if var
contains a
type and I write std::get<b>(var)
I get an exception. So I thought about this solution:
try {
std::variant<a>(var);
// Do something
} catch(const std::bad_variant_access&) {
try {
std::variant<b>(var);
// Do something else
} catch(const std::bad_variant_access&) {
try {
std::variant<c>(var);
// Another else
} catch (const std::bad_variant_access&) {
// std::monostate
}
}
}
But it's so complicated and ugly! Is there a simpler way to check what type std::variant
contains?
Since this function is specific to a given type, you don't need RTTI to perform the operations required by std::any .
The class template std::variant represents a type-safe union. An instance of std::variant at any given time either holds a value of one of its alternative types, or in the case of error - no value (this state is hard to achieve, see valueless_by_exception).
According to cppreference ::std::variant must not allocate dynamic memory. As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.
std::monostate is a value-semantic type, like bool — it just has one fewer value in its domain. It can be stored in variants, or containers, or set s (it's ordered!), or unordered_set s (it's hashable!), or anywhere else you might use a value-semantic type like bool . It's totally fine to make an optional<monostate> .
std::visit
is the way to go:
There is even overloaded
to allow inlined visitor:
// helper type for the visitor #4
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
and so:
std::visit(overloaded{
[](std::monostate&){/*..*/},
[](a&){/*..*/},
[](b&){/*..*/},
[](c&){/*..*/}
}, var);
To use chained if-branches instead, you might used std::get_if
if (auto* v = std::get_if<a>(var)) {
// ...
} else if (auto* v = std::get_if<b>(var)) {
// ...
} else if (auto* v = std::get_if<c>(var)) {
// ...
} else { // std::monostate
// ...
}
The most simple way is to switch
based on the current std::variant::index()
. This approach requires your types (std::monostate
, A
, B
, C
) to always stay in the same order.
// I omitted C to keep the example simpler, the principle is the same
using my_variant = std::variant<std::monostate, A, B>;
void foo(my_variant &v) {
switch (v.index()) {
case 0: break; // do nothing because the type is std::monostate
case 1: {
doSomethingWith(std::get<A>(v));
break;
}
case 2: {
doSomethingElseWith(std::get<B>(v));
break;
}
}
}
If your callable works with any type, you can also use std::visit
:
void bar(my_variant &v) {
std::visit([](auto &&arg) -> void {
// Here, arg is std::monostate, A or B
// This lambda needs to compile with all three options.
// The lambda returns void because we don't modify the variant, so
// we could also use const& arg.
}, v);
}
If you don't want std::visit
to accept std::monostate
, then just check if the index
is 0. Once again, this relies on std::monostate
being the first type of the variant, so it is good practice to always make it the first.
You can also detect the type using if-constexpr
inside the callable. With this approach, the arguments don't have to be in the same order anymore:
void bar(my_variant &v) {
std::visit([](auto &&arg) -> my_variant {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<std::monostate, T>) {
return arg; // arg is std::monostate here
}
else if constexpr (std::is_same_v<A, T>) {
return arg + arg; // arg is A here
}
else if constexpr (std::is_same_v<B, T>) {
return arg * arg; // arg is B here
}
}, v);
}
Note that the first lambda returns void
because it just processes the current value of the variant. If you want to modify the variant, your lambda needs to return my_variant
again.
You could use an overloaded visitor inside std::visit
to handle A
or B
separately. See std::visit
for more examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With