The way std::variant
dispatches to different visitor methods when std::visit
is called is pretty reasonable when the variant alternatives are compl
For what it's worth, a totally handrolled visit with a switch
does pretty well:
// use a code generator to write out all of these
template <typename F, typename V>
auto custom_visit(F f, V&& v, std::integral_constant<size_t, 2> )
{
switch (v.index()) {
case 0: return f(std::get<0>(std::forward<V>(v)));
case 1: return f(std::get<1>(std::forward<V>(v)));
#ifdef VALUELESS
case std::variant_npos: {
[]() [[gnu::cold, gnu::noinline]] {
throw std::bad_variant_access();
}();
}
#endif
}
__builtin_unreachable();
}
template <typename F, typename V>
auto custom_visit(F f, V&& v) {
return custom_visit(f, std::forward<V>(v),
std::variant_size<std::decay_t<V>>{});
}
Which you'd use like:
int getBaseMemVariant2(Foobar& v) {
return custom_visit([](Base& b){ return &b; }, v)->getBaseMember();
}
With VALUELESS
, this emits:
getBaseMemVariant2(std::variant<Foo, Bar>&):
movzx eax, BYTE PTR [rdi+8]
cmp al, -1
je .L27
cmp al, 1
ja .L28
mov eax, DWORD PTR [rdi]
ret
.L27:
sub rsp, 8
call auto custom_visit<getBaseMemVariant2(std::variant<Foo, Bar>&)::{lambda(Base&)#1}, std::variant<Foo, Bar>&>(getBaseMemVariant2(std::variant<Foo, Bar>&)::{lambda(Base&)#1}, std::variant<Foo, Bar>&, std::integral_constant<unsigned long, 2ul>)::{lambda()#1}::operator()() const [clone .isra.1]
Which is pretty good. Without VALUELESS
, this emits :
getBaseMemVariant2(std::variant<Foo, Bar>&):
mov eax, DWORD PTR [rdi]
ret
as desired.
I don't really know what conclusion, if any, to draw from this. Clearly, there's hope?