According to cppreference std::get
for variant
throws std::bad_variant_access
if the type contained in the variant
is not the expected one. This means that the standard library has to check on every access (libc++).
What was the rationale for this decision? Why is it not undefined behavior, like everywhere else in C++? Can I work around it?
Undefined behavior exists mainly to give the compiler freedom to optimize. One thing it allows the compiler to do, for example, is to operate under the assumption that certain things can't happen (without having to first prove that they can't happen, which would often be very difficult or impossible).
No, very explicitly. From [variant. variant]: Any instance of variant at any given time either holds a value of one of its alternative types, or it holds no value.
std::variant (C++17)A std::variant is a type-safe union. An instance of std::variant has a value from one of its types. The value must not be a reference, C-array or void. A std::variant can have one type more than once.
The current API for std::variant
has no unchecked version of std::get
. I don't know why it was standardized that way; anything I say would just be guessing.
However, you can get close to the desired behavior by writing *std::get_if<T>(&variant)
. If variant
doesn't hold T
at that time, std::get_if<T>
returns nullptr
, so dereferencing it is undefined behavior. The compiler can thus assume that the variant holds T
.
In practice, this isn't the easiest optimization for the compiler to do. Compared to a simple tagged union, the code it emits may not be as good. The following code:
int const& get_int(std::variant<int, std::string> const& variant) { return *std::get_if<int>(&variant); }
Emits this with clang 5.0.0:
get_int(std::variant<int, std::string> const&): xor eax, eax cmp dword ptr [rdi + 24], 0 cmove rax, rdi ret
It is comparing the variant's index and conditionally moving the return value when the index is correct. Even though it would be UB for the index to be incorrect, clang is currently unable to optimize the comparison away.
Interestingly, returning an int
instead of a reference optimizes the check away:
int get_int(std::variant<int, std::string> const& variant) { return *std::get_if<int>(&variant); }
Emits:
get_int(std::variant<int, std::string> const&): mov eax, dword ptr [rdi] ret
You could help the compiler by using __builtin_unreachable()
or __assume
, but gcc is currently the only compiler capable of removing the checks when you do so.
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