【问题标题】:How does std::visit handle multiple variants?std::visit 如何处理多个变体?
【发布时间】:2021-12-08 01:16:04
【问题描述】:

How does std::visit work with std::variant?

的问题相关,但不是同一个问题

为单个变体实现 std::visit 在概念上看起来像这样(在 C++ 伪代码中):

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);
};

基本上,我们设置了一个跳转表,为我们的访问者调用正确的调度程序。

对于多个变体,它似乎更复杂,因为对于这种方法,您需要计算所有变体备选方案的笛卡尔积,并且可能需要数千个函数的模板。这是它的完成方式还是标准库实现它的更好方式?

【问题讨论】:

  • 是什么让您相信需要笛卡尔积?所需要做的就是一次发送一个变体。
  • @SamVarshavchik 如何在运算符 a, aa, b 之间解析?显然,您必须根据第二个变体的有效值选择其中一个
  • 不,std::visit 不是这样工作的,有多种变体。
  • @samv 不,可以证明 std 访问需要笛卡尔积。我不确定你认为它有什么作用。

标签: c++ template-meta-programming


【解决方案1】:

对于多个变体,似乎比较复杂,至于这个 方法你需要计算所有的笛卡尔积 变体的替代方案,可能还有数千个函数的模板。

目前有两种多变量访问的实现。

GCC在编译时根据变体替代类型的数量构造一个multi-dimensional function table,并在运行时通过多个索引访问对应的访问函数。

// 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 使用recursive switch-case 执行多变量访问:

#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;                                                          \
    }

这种 switch-case base 实现在变体数量较少或备选方案数量较少时具有更好的性能,这也使得 GCC 在its recent patch 中使用这种方法处理简单的情况,以减少函数表的实例化。

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:
          // ...
}

更多实现细节可以参考以下两篇 Michael Park 的博客:

【讨论】:

    猜你喜欢
    • 2017-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 2021-03-25
    • 2016-07-18
    • 1970-01-01
    相关资源
    最近更新 更多