Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why std::get for variant throws on failure instead of being undefined behaviour?

Tags:

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?

like image 703
Denis Yaroshevskiy Avatar asked Feb 15 '18 22:02

Denis Yaroshevskiy


People also ask

Why does C++ allow undefined behavior?

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

Does std :: variant allocate?

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.

How does STD variant work?

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.


1 Answers

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.

like image 93
Justin Avatar answered Sep 25 '22 00:09

Justin