I'm trying to use CRTP with std::variant to achieve a classic Expression class, for example in some algebraic data type
data Expr = Num Int
| Add Expr Expr
| Sub Expr Expr
...
I use CRTP to avoid virtual methods
template <typename Impl>
struct Expr {
int eval() { return static_cast<Impl *>(this)->eval_impl(); }
};
and I choose std::variant to store a the subexpressions in Add and 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); }
};
the question is, it doesn't compile (saying that Sub isn't complete type and many other things), but it did work with only two of the variants (Num and Add). I know the code looks strange, it'd be appreciated if you can explain how to implement the above ADT in a cleaner way (probably without vtables).
the full code:
#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;
}