我正在尝试将 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
来存储Add
和Sub
中的子表达式
// 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/