我正在编写解析器并使用 std::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
作为具有公共(public)字段和单独的 std::variant
的结构成员。 Node
中分别定义字段替代方法,对于每个字段,使用 const auto &
定义访问者选择该字段的成员。换句话说,只使用访问者。 还有其他方法吗?我真正想做的是用字段定义一个抽象基类,拥有所有
Node
替代品(Add
等)从该类继承,然后可以说,“我不知道这个 std::variant
包含什么替代品,但我知道它们都是这个基类的实例,我只是想用它作为那个类的一个实例。”
最佳答案
假设变体中的每种类型都有一些字段 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
请注意,您不必手动添加重载 -
auto
在 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&)
可悲的是,使用公共(public)基类并不会导致编译器意识到所有案例都有相同的代码 - 你仍然保留跳转表:https://godbolt.org/z/GTyNgP
理论上,您可以使用自定义的类似变体的类型删除数据结构,它知道所有具有公共(public)基类的类型并通过它来代替。但这朝着只有
NodeBase
的方向发展。具有通用接口(interface)等的类
关于c++ - 如何以多态方式使用替代类型的 std::variant,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59268812/