Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does std::visit handle multiple variants?

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?

like image 413
itzjackyscode Avatar asked Dec 08 '21 01:12

itzjackyscode


People also ask

Does std :: variant allocate?

According to cppreference ::std::variant must not allocate dynamic memory.

How does STD variant work?

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.

What is the difference between STD::variant and STD::visit?

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.

What is the use of visit function in C++?

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.

Can variants have the same type more than 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.

What are some examples of use cases for using variants?

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.


Video Answer


1 Answers

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:

  • Variant Visitation
  • Variant Visitation V2
like image 96
康桓瑋 Avatar answered Oct 21 '22 07:10

康桓瑋