Skip to content

Instantly share code, notes, and snippets.

@s3rvac
Last active May 6, 2024 08:46
Show Gist options
  • Save s3rvac/ddb2a0ffc6bcc1d3b2f6 to your computer and use it in GitHub Desktop.
Save s3rvac/ddb2a0ffc6bcc1d3b2f6 to your computer and use it in GitHub Desktop.
An example of using double dispatch in C++ to implement expression evaluation without type casts.
// $ g++ -std=c++14 -pedantic -Wall -Wextra double-dispatch.cpp -o double-dispatch
// $ ./double-dispatch
//
// Also works under C++11.
#include <iostream>
#include <memory>
#if __cplusplus == 201103L
namespace std {
// std::make_unique from C++14 when compiling under C++11
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
}
#endif
class Constant;
class ConstInt;
class ConstFloat;
class Expression {
public:
virtual ~Expression() = default;
virtual std::unique_ptr<Constant> evaluate() const = 0;
virtual void printTo(std::ostream &os) const = 0;
protected:
Expression() = default;
};
class Constant: public Expression {
public:
// operator+() does `this + other`, while addTo() performs `other + this`. The
// reason behind using different function names for these two cases is to
// keep track which operand is the on the left-hand side and which one is
// on the right-hand side. For addition, this is irrelevant, but for
// non-commutative operations, like subtraction, it makes a difference.
// Keep in mind that the implementation of double dispatch in C++ swaps the
// order of operands. See the implementation of e.g. ConstInt::operator+()
// and ConstInt::addTo().
// For subtraction, we would have operator-() and subtractFrom(), which
// would indicate which operand is the left one and which is the right one.
virtual std::unique_ptr<Constant> operator+(const Constant &other) const = 0;
virtual std::unique_ptr<Constant> addTo(const ConstInt &other) const = 0;
virtual std::unique_ptr<Constant> addTo(const ConstFloat &other) const = 0;
protected:
Constant() = default;
};
class ConstInt: public Constant {
public:
ConstInt(int value): value(value) {}
virtual ~ConstInt() override = default;
static std::unique_ptr<ConstInt> create(int value);
int getValue() const { return value; }
virtual std::unique_ptr<Constant> operator+(const Constant &other) const override;
virtual std::unique_ptr<Constant> addTo(const ConstInt &other) const override;
virtual std::unique_ptr<Constant> addTo(const ConstFloat &other) const override;
virtual std::unique_ptr<Constant> evaluate() const override;
virtual void printTo(std::ostream &os) const override;
private:
int value;
};
class ConstFloat: public Constant {
public:
ConstFloat(float value): value(value) {}
virtual ~ConstFloat() override = default;
static std::unique_ptr<ConstFloat> create(float value);
float getValue() const { return value; }
virtual std::unique_ptr<Constant> operator+(const Constant &other) const override;
virtual std::unique_ptr<Constant> addTo(const ConstInt &other) const override;
virtual std::unique_ptr<Constant> addTo(const ConstFloat &other) const override;
virtual std::unique_ptr<Constant> evaluate() const override;
virtual void printTo(std::ostream &os) const override;
private:
float value;
};
class AddExpr: public Expression {
public:
AddExpr(std::shared_ptr<Expression> op1, std::shared_ptr<Expression> op2):
op1(op1), op2(op2) {}
virtual ~AddExpr() override = default;
static std::unique_ptr<AddExpr> create(
std::shared_ptr<Expression> op1,
std::shared_ptr<Expression> op2
);
virtual std::unique_ptr<Constant> evaluate() const override;
virtual void printTo(std::ostream &os) const override;
private:
std::shared_ptr<Expression> op1;
std::shared_ptr<Expression> op2;
};
std::ostream &operator<<(std::ostream &os, const Expression &expr) {
// http://stackoverflow.com/questions/4571611/making-operator-virtual
expr.printTo(os);
return os;
}
//
// AddExpr
//
std::unique_ptr<AddExpr> AddExpr::create(std::shared_ptr<Expression> op1,
std::shared_ptr<Expression> op2) {
return std::make_unique<AddExpr>(op1, op2);
}
std::unique_ptr<Constant> AddExpr::evaluate() const {
return *op1->evaluate() + *op2->evaluate();
}
void AddExpr::printTo(std::ostream &os) const {
os << "(" << *op1 << " + " << *op2 << ")";
}
//
// ConstInt
//
std::unique_ptr<ConstInt> ConstInt::create(int value) {
return std::make_unique<ConstInt>(value);
}
std::unique_ptr<Constant> ConstInt::operator+(const Constant &other) const {
return other.addTo(*this);
}
std::unique_ptr<Constant> ConstInt::addTo(const ConstInt &other) const {
return ConstInt::create(other.getValue() + value);
}
std::unique_ptr<Constant> ConstInt::addTo(const ConstFloat &other) const {
return ConstFloat::create(other.getValue() + value);
}
std::unique_ptr<Constant> ConstInt::evaluate() const {
return ConstInt::create(value);
}
void ConstInt::printTo(std::ostream &os) const {
os << value;
}
//
// ConstFloat
//
std::unique_ptr<ConstFloat> ConstFloat::create(float value) {
return std::make_unique<ConstFloat>(value);
}
std::unique_ptr<Constant> ConstFloat::operator+(const Constant &other) const {
return other.addTo(*this);
}
std::unique_ptr<Constant> ConstFloat::addTo(const ConstInt &other) const {
return ConstFloat::create(other.getValue() + value);
}
std::unique_ptr<Constant> ConstFloat::addTo(const ConstFloat &other) const {
return ConstFloat::create(other.getValue() + value);
}
std::unique_ptr<Constant> ConstFloat::evaluate() const {
return ConstFloat::create(value);
}
void ConstFloat::printTo(std::ostream &os) const {
os << value;
}
//
// Main
//
int main() {
auto expr = AddExpr::create(
ConstInt::create(1),
AddExpr::create(
ConstFloat::create(3.1),
ConstInt::create(2)
)
);
auto result = expr->evaluate();
std::cout << *expr << " = " << *result << "\n";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment