std::visit 如何处理多个变体?
How does std::visit handle multiple variants?
与
相关但不是同一个问题
为单个变体实现 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);
};
基本上,我们设置了一个跳转 table 来为我们的访问者调用正确的调度程序。
对于多个变体,它似乎更复杂,因为对于这种方法,您需要计算所有变体的备选方案的笛卡尔积,并且可能模板化数千个函数。这是它的完成方式还是标准库有更好的实现方式?
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.
目前有两种多变量访问的实现方式。
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 的以下两篇博客了解更多实现细节:
与
为单个变体实现 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);
};
基本上,我们设置了一个跳转 table 来为我们的访问者调用正确的调度程序。
对于多个变体,它似乎更复杂,因为对于这种方法,您需要计算所有变体的备选方案的笛卡尔积,并且可能模板化数千个函数。这是它的完成方式还是标准库有更好的实现方式?
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.
目前有两种多变量访问的实现方式。
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 的以下两篇博客了解更多实现细节: