如何多态地使用 std::variant 的替代类型

How to use alternative types of std::variant polymorphically

我正在编写解析器并使用 std::variant 来表示 AST 的节点。对于基本表达式,我有类似的东西:

struct Add;
struct Sub;
struct Mul;
struct Div;
typedef std::variant<Add, Sub, Mul, Div> Node;

(实际的结构定义在后面,因为其中一些引用了Node。)

我发现这种方法比将 Node 声明为抽象基础 class 和 Add 等作为子 class 更好,因为它使我能够分离逻辑从 AST 本身使用 AST。例如,计算表达式的代码可以使用 std::visit 而无需 Node 需要虚拟 evaluate 方法或进行类型检查和向下转换。

我的问题是有些字段我希望每个 Node 都有,我希望在使用这些字段时能够对所有 Node 变体一视同仁。

我唯一的策略是:

还有其他办法吗?我真正想做的是用字段定义一个抽象基础 class,让所有 Node 替代项(Add 等)继承自那个 class,并且然后可以说,“我不知道这个 std::variant 包含什么替代方案,但我知道它们都是这个基础 class 的实例,我只想将它用作class。”

暂时假设基数 class 偏移量会因 class 而不同(通常不能保证基数 classes 在 C++ 中的偏移量为零)。显然,这意味着您需要知道存储在变体中的实际类型才能找到基础 class 子对象。

一旦你意识到这一点,你就会明白为什么你的选择有效:

  • 如果公共字段是 Node 的一部分,它们始终处于 Node
  • 中的固定偏移量
  • 如果您使用访问者,它会找到实际类型,因此可以计算每个类型到基础 class 子对象的偏移量。

假设变体中的每个类型都有一些字段std::string_view token;(这是在每个类型中写出来的还是来自一个公共的,不一定是多态的,基础class由你决定),您可以像下面这样编写访问器:

std::string_view getToken(const Node& node)
{
  return std::visit([](const auto& n) { return n.token; }, node);
}

https://godbolt.org/z/B8tAdY

请注意,您不必手动添加重载 - lambda 中的 auto 实质上使 lambda 成为模板,因此您只需依赖编译时多态性。你最终得到了一个很好的跳跃 table (每个都只返回适当的成员偏移量):

std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 0ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&): # @"std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 0ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)"
        mov     rax, qword ptr [rsi + 8]
        mov     rdx, qword ptr [rsi + 16]
        ret
std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 1ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&): # @"std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 1ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)"
        mov     rax, qword ptr [rsi + 16]
        mov     rdx, qword ptr [rsi + 24]
        ret
std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 2ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&): # @"std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 2ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)"
        mov     rax, qword ptr [rsi + 8]
        mov     rdx, qword ptr [rsi + 16]
        ret
std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 3ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&): # @"std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 3ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)"
        mov     rax, qword ptr [rsi + 16]
        mov     rdx, qword ptr [rsi + 24]
        ret

std::__detail::__variant::__gen_vtable<true, std::basic_string_view<char, std::char_traits<char> >, getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&>::_S_vtable:
        .quad   std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 0ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)
        .quad   std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 1ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)
        .quad   std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 2ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)
        .quad   std::__detail::__variant::__gen_vtable_impl<true, std::__detail::__variant::_Multi_array<std::basic_string_view<char, std::char_traits<char> > (*)(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)>, std::tuple<std::variant<Add, Sub, Mul, Div> const&>, std::integer_sequence<unsigned long, 3ul> >::__visit_invoke(getToken(std::variant<Add, Sub, Mul, Div> const&)::$_0&&, std::variant<Add, Sub, Mul, Div> const&)

遗憾的是,使用公共基础 class 不会导致编译器意识到所有情况都有相同的代码 - 你仍然保持跳转 table:https://godbolt.org/z/GTyNgP

从理论上讲,您可以使用自定义的类似变体的类型擦除数据结构,该数据结构知道所有具有共同基础的类型 class 并通过它来代替。但这朝着 NodeBase class 和通用接口等

的方向发展