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));
}
cmp
andjne
are not there. (Though I will say that I am impressed the compiler pulled this off, it might be close enough, if not ideal) – Varicella__builtin_unreachable()
instead ofterminate()
. Clang's codegen is terrible with that though. – Antiserum