c++ - 有效使用带有两个元素的 CRTP std::variant,但不能再多了

标签 c++ functional-programming c++17 variant crtp

我正在尝试将 CRTP 与 std::variant 结合使用来实现经典的 Expression 类,例如在某些代数数据类型中

data Expr = Num Int
     | Add Expr Expr
     | Sub Expr Expr
     ...

我使用 CRTP 来避免虚拟方法

template <typename Impl>
struct Expr {
    int eval() { return static_cast<Impl *>(this)->eval_impl(); }
};

我选择std::variant来存储AddSub中的子表达式

// forward declarations
using expr_variant = std::variant<Num, Add, Sub>;

struct Num : Expr<Num> {
    int eval_impl() const { return n; }
    Num(int n) : n(n) {}
    int n;
};
struct Add : Expr<Add>
{
    std::shared_ptr<expr_variant> l, r;
    Add(const expr_variant &l, const expr_variant &r) : l{std::make_shared<expr_variant>(l)}, r{std::make_shared<expr_variant>(r)} {}
    int eval_impl() const { return std::visit(visitor{}, *l) + std::visit(visitor{}, *r); }
};
struct Sub : Expr<Sub> {
    std::shared_ptr<expr_variant> l, r;
    Sub(const expr_variant &l, const expr_variant &r) : l{std::make_shared<expr_variant>(l)}, r{std::make_shared<expr_variant>(r)} {}
    int eval_impl() const { return std::visit(visitor{}, *l) - std::visit(visitor{}, *r); }
};

问题是,它无法编译(说 Sub 不是完整的类型和许多其他东西),但它确实只适用于其中两个变体(Num添加)。我知道代码看起来很奇怪,如果您能解释如何以更清晰的方式实现上述 ADT(可能不需要 vtable),我将不胜感激。

完整代码:

#include <variant>
#include <memory>

template <typename Impl>
struct Expr {
    int eval() { return static_cast<Impl *>(this)->eval_impl(); }
};
struct Add;
struct Sub;
struct Num;
struct visitor {
    int operator()(auto &e) const { return e.eval(); }
};
using expr_variant = std::variant<Num, Add, Sub>;

struct Num : Expr<Num> {
    int eval_impl() const { return n; }
    Num(int n) : n(n) {}
    int n;
};
struct Add : Expr<Add>
{
    std::shared_ptr<expr_variant> l, r;
    Add(const expr_variant &l, const expr_variant &r) : l{std::make_shared<expr_variant>(l)}, r{std::make_shared<expr_variant>(r)} {}
    int eval_impl() const { return std::visit(visitor{}, *l) + std::visit(visitor{}, *r); }
};
struct Sub : Expr<Sub> {
    std::shared_ptr<expr_variant> l, r;
    Sub(const expr_variant &l, const expr_variant &r) : l{std::make_shared<expr_variant>(l)}, r{std::make_shared<expr_variant>(r)} {}
    int eval_impl() const { return std::visit(visitor{}, *l) - std::visit(visitor{}, *r); }
};

int main()
{
    Sub e{Add{Num(45600), Num{70}}, Num{9}};
    return 0;
}

最佳答案

std::variant 不能与不完整类型一起使用。当您在 struct Add 主体中有 Add::eval_impl 的内联定义时,Num 已完成且 Add > 已完成,但 Sub 尚未定义(这就是为什么它在只有 2 种类型时有效)。

将这些使用变体的成员函数的定义移至所有类完成后:

using expr_variant = std::variant<Num, Add, Sub>;

struct Num : Expr<Num> {
    inline int eval_impl() const;
    Num(int n) : n(n) {}
    int n;
};
struct Add : Expr<Add>
{
    std::shared_ptr<expr_variant> l, r;
    Add(const expr_variant &l, const expr_variant &r) : l{std::make_shared<expr_variant>(l)}, r{std::make_shared<expr_variant>(r)} {}
    inline int eval_impl() const;
};
struct Sub : Expr<Sub> {
    std::shared_ptr<expr_variant> l, r;
    Sub(const expr_variant &l, const expr_variant &r) : l{std::make_shared<expr_variant>(l)}, r{std::make_shared<expr_variant>(r)} {}
    inline int eval_impl() const;
};

// All classes are now complete
int Num::eval_impl() const {
    return n;  // (This could still be in-class, but for consistency)
}
int Add::eval_impl() const {
    return std::visit(visitor{}, *l) + std::visit(visitor{}, *r);
}
int Sub::eval_impl() const {
    return std::visit(visitor{}, *l) - std::visit(visitor{}, *r);
}

关于c++ - 有效使用带有两个元素的 CRTP std::variant,但不能再多了,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71708921/

相关文章:

c++ - 转发声明 : templates and inheritance

c++ - 在 C++ 中写入现有的空格二进制文件

inheritance - 参数多态性与子类型多态性 F#

haskell - 关于映射到多参数函数的最终结果

c++ - 为成员对象内联使用非默认显式构造函数

c++ - 什么是节点句柄?

c++ - 错误 : expected ')' before 'data'

c++ - 插入排序打印错误输出

haskell - 有没有办法在纯函数式语言中调用两个函数(一个接一个)? (在非 io 模式下)

c++ - std::array<std::vector> 中的大括号省略