4

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

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

(实际的结构定义稍后会出现,因为其中一些引用了Node.)

我发现这种方法比Node将抽象基类Add等声明为子类更好,因为它使我能够将使用 AST 的逻辑与 AST 本身分开。例如,评估表达式的代码可以std::visit在不需要Node虚拟evaluate方法或进行类型检查和向下转换的情况下使用。

我的问题是,我希望每个Node人都拥有一些字段,并且我希望在使用这些字段时能够以相同的方式对待所有 Node 变体。

我带来的唯一策略是:

  • 定义Node为具有公共字段和单独std::variant成员的结构。
  • 在每个备选方案中分别定义字段,并为每个字段定义一个具有选择该字段Node的成员的访问者。const auto &换句话说,只使用访问者。

还有其他方法吗?我真正想做的是定义一个带有字段的抽象基类,让所有Node替代品(Add等)从该类继承,然后能够说,“我不知道它std::variant包含什么替代品,但是我知道它们都是这个基类的实例,我只想把它当作那个类的一个实例。”</p>

4

2 回答 2

3

假设变体中的每种类型都有一些字段std::string_view token;(无论是在每个类型中写出还是来自一个共同的,不一定是多态的,基类取决于您),您可以编写如下访问器:

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

https://godbolt.org/z/B8tAdY

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

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&)

可悲的是,使用通用基类不会导致编译器意识到所有案例都有相同的代码 - 你仍然保留跳转表:https ://godbolt.org/z/GTyNgP

理论上,您可以使用自定义的类似变体的类型擦除数据结构,它知道所有具有公共基类的类型并通过它来代替。但这朝着只拥有一个NodeBase具有公共接口等的类的方向发展。

于 2019-12-10T14:22:25.787 回答
2

假设每个类的基类偏移量会有所不同(通常不能保证基类在 C++ 中的偏移量为零)。显然,这意味着您需要知道存储在变体中的实际类型才能定位基类子对象。

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

  • 如果公共字段是 的一部分Node,则它们始终处于固定偏移量Node
  • 如果您使用访问者,它会找到实际类型,因此可以计算每个类型到基类子对象的偏移量。
于 2019-12-10T14:03:58.860 回答