Last active
May 6, 2024 08:46
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $ 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