c++ - 如何以多态方式使用替代类型的 std::variant

标签 c++ std-variant

我正在编写解析器并使用 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/

    相关文章:

    c++ - Fmod 函数显然输出预期的 double 值,但 if(fmod == expected double) 的计算结果不为真

    c++ - 尝试将值存储到映射中的std::variant时,std::bad_variant_access错误

    c++ - 在构造 std::variant 时禁用从指针类型到 bool 的隐式转换的最佳方法是什么?

    c++ - 将变体转换为 super 集变体或子集变体

    c++ - 传递给 std::variant 的预定义类型列表

    c++ - 有没有办法在Windows下编译为Linux编写的C++代码?

    c++ - 数组帮助中插入和修改函数的示例 (c++)

    c++ - 在 C++ UWP 项目中调用 OutputDebugString

    c++ - 实现嵌套在模板类中的类的成员函数