Related to, but not the same question as How does std::visit work with std::variant?
Implementing std::visit
for a single variant conceptually looks like this (in C++ pseudocode):
template<class F, class... Ts>
void visit(F&& f, variant<Ts...>&& var) {
using caller_type = void(*)(void*);
caller_type dispatch[] = {dispatch_visitor(f, (INDEX_SEQUENCE))...};
dispatch[var.index()](var);
};
Basically, we set up a jump table that calls the correct dispatcher for our visitor.
For multiple variants, it seems to be more complicated, as for this approach you'd need to compute the Cartesian product of all the variants' alternatives and possibly template thousands of functions. Is this how it's done or is there a better way that standard libraries implement it?
According to cppreference ::std::variant must not allocate dynamic memory.
std::variant (C++17) 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. A default-initialized std::variant will be initialized with its first type.
Modern C++ Features - std::variant and std::visit - Simplify C++! std::variant is a library addition in C++17 for sum types, and std::visit is one of the ways to process the values in a std::variant.
std::visit is a powerful utility that allows you to call a function over a currently active type in std::variant. It does some magic to select the proper overload, and what's more, it can support many variants at once.
Variants can have the same type more than once in their type list, e.g. std::variant<int, double, int>. In that case, the access by type is ambiguous and not allowed.
Let's have a look at a few examples of how to use this functionality. Here's a basic example with one variant: We have a variant that represents a package with four various types, and then we use super-advanced VisitPackage structure to detect what's inside.
For multiple variants, it seems to be more complicated, as for this approach you'd need to compute the Cartesian product of all the variants' alternatives and possibly template thousands of functions.
There are currently two implementations of multi-variant visitation.
GCC constructs a multi-dimensional function table at compile time according to the number of alternative types of variants, and access the corresponding visit function through multiple indexes at runtime.
// Use a jump table for the general case.
constexpr auto& __vtable = __detail::__variant::__gen_vtable<
_Result_type, _Visitor&&, _Variants&&...>::_S_vtable;
auto __func_ptr = __vtable._M_access(__variants.index()...);
return (*__func_ptr)(std::forward<_Visitor>(__visitor),
std::forward<_Variants>(__variants)...);
MSVC uses recursive switch-case to perform multi-variant visitation:
#define _STL_VISIT_STAMP(stamper, n) \
constexpr size_t _Size = _Variant_total_states<_Remove_cvref_t<_Variants>...>; \
static_assert(_Size > (n) / 4 && _Size <= (n)); \
switch (_Idx) { \
stamper(0, _STL_CASE); \
default: \
_STL_UNREACHABLE; \
}
This switch-case base implementation has better performance when the number of variants is small or the number of alternatives is small, which also makes GCC use this approach for simple cases in its recent patch to reduce the instantiation of function tables.
switch (__v0.index())
{
_GLIBCXX_VISIT_CASE(0)
_GLIBCXX_VISIT_CASE(1)
_GLIBCXX_VISIT_CASE(2)
_GLIBCXX_VISIT_CASE(3)
_GLIBCXX_VISIT_CASE(4)
case variant_npos:
// ...
}
You can refer to the following two blogs by Michael Park for more implementation details:
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