I'm in the process of updating a codebase that is currently using a custom equivalent of std::variant
to C++17 .
In certain parts of the code, the variant is being reset from a known alternative, so the class provides a method that asserts that index()
is at a current value, but still directly invokes the proper destructor unconditionally.
This is used in some tight inner loops, and has (measured) non-trivial performance impact. That's because it allows the compiler to eliminate the entire destruction when the alternative in question is a trivially destructible type.
At face value, it seems to me that I can't achieve this with the current std::variant<>
implementation in the STL, but I'm hoping that I'm wrong.
Is there a way to accomplish this that I'm not seeing, or am I out of luck?
Edit: as requested, here's a usage example (using @T.C's example as basis):
struct S {
~S();
};
using var = MyVariant<S, int, double>;
void change_int_to_double(var& v){
v.reset_from<1>(0.0);
}
change_int_to_double
compiles to effectively:
@change_int_to_double(MyVariant<S, int, double>&)
mov qword ptr [rdi], 0 // Sets the storage to double(0.0)
mov dword ptr [rdi + 8], 2 // Sets the index to 2
Edit #2
Thanks to various insight from @T.C., I've landed on this monstrosity. It "works" even though it does violate the standard by skipping a few destructors. However, every skipped destructor is checked at compile-time to be trivial so...:
see on godbolt: https://godbolt.org/g/2LK2fa
// Let's make sure our std::variant implementation does nothing funky internally.
static_assert(std::is_trivially_destructible<std::variant<char, int>>::value,
"change_from_I won't be valid");
template<size_t I, typename arg_t, typename... VAR_ARGS>
void change_from_I(std::variant<VAR_ARGS...>& v, arg_t&& new_val) {
assert(I == v.index());
// Optimize away the std::get<> runtime check if possible.
#if defined(__GNUC__)
if(v.index() != I) __builtin_unreachable();
#else
if(v.index() != I) std::terminate();
#endif
// Smart compilers handle this fine without this check, but MSVC can
// use the help.
using current_t = std::variant_alternative_t<I, std::variant<VAR_ARGS...>>;
if(!std::is_trivially_destructible<current_t>::value) {
std::get<I>(v).~current_t();
}
new (&v) var(std::forward<arg_t>(new_val));
}
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.
union s save memory because they allow a single piece of memory to be used for different types of objects at different times. Consequently, they can be used to save memory when we have several objects that are never used at the same time. std::variant uses the memory similar to the union .
A monostate is a "conceptual singleton" - all data members of a monostate are static, so all instances of the monostate use the same (static) data. Applications using a monostate can create any number of instances that they desire, as each instance uses the same data.
#include <variant>
struct S {
~S();
};
using var = std::variant<S, int, double>;
void change_int_to_double(var& v){
if(v.index() != 1) __builtin_unreachable();
v = 0.0;
}
GCC compiles the function down to:
change_int_to_double(std::variant<S, int, double>&):
mov QWORD PTR [rdi], 0x000000000
mov BYTE PTR [rdi+8], 2
ret
which is optimal. Clang's codegen, OTOH, leaves much to be desired, although it isn't too bad if you use std::terminate()
(the equivalent of an assertion) rather than __builtin_unreachable()
:
change_int_to_double(std::__1::variant<S, int, double>&): # @change_int_to_double(std::__1::variant<S, int, double>&)
cmp dword ptr [rdi + 8], 1
jne .LBB0_2
mov qword ptr [rdi], 0
mov dword ptr [rdi + 8], 2
ret
.LBB0_2:
push rax
call std::terminate()
MSVC...let's not talk about MSVC.
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