Last active
December 5, 2016 00:55
-
-
Save mpark/6941d3f4aba4458b1121b03dbb4861da to your computer and use it in GitHub Desktop.
Visitor Pattern
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
class Cat; | |
class Dog; | |
class Animal { | |
public: | |
template <typename F> | |
static void visit(const F& f, const Animal& animal) { | |
animal.accept(Visitor<F>(f)); | |
} | |
template <typename F> | |
static void visit(const F& f, const Animal& lhs, const Animal& rhs) { | |
lhs.accept(LhsVisitor<F>(f, rhs)); | |
} | |
protected: | |
struct AnyVisitor { | |
virtual void operator()(const Cat&) const = 0; | |
virtual void operator()(const Dog&) const = 0; | |
}; | |
private: | |
template <typename F> | |
struct Visitor : AnyVisitor { | |
explicit Visitor(const F& f) : f_(f) {} | |
virtual void operator()(const Cat& that) const override { f_(that); } | |
virtual void operator()(const Dog& that) const override { f_(that); } | |
const F& f_; | |
}; | |
template <typename F, typename Lhs> | |
struct RhsVisitor : AnyVisitor { | |
explicit RhsVisitor(const F& f, const Lhs& lhs) : f_(f), lhs_(lhs) {} | |
virtual void operator()(const Cat& rhs) const override { f_(lhs_, rhs); } | |
virtual void operator()(const Dog& rhs) const override { f_(lhs_, rhs); } | |
const F& f_; | |
const Lhs& lhs_; | |
}; | |
template <typename F> | |
struct LhsVisitor : AnyVisitor { | |
explicit LhsVisitor(const F& f, const Animal& rhs) : f_(f), rhs_(rhs) {} | |
virtual void operator()(const Cat& lhs) const override { | |
rhs_.accept(RhsVisitor<F, Cat>(f_, lhs)); | |
} | |
virtual void operator()(const Dog& lhs) const override { | |
rhs_.accept(RhsVisitor<F, Dog>(f_, lhs)); | |
} | |
private: | |
const F& f_; | |
const Animal& rhs_; | |
}; | |
virtual void accept(const AnyVisitor&) const = 0; | |
}; | |
class Cat : public Animal { | |
private: | |
virtual void accept(const AnyVisitor& visitor) const { visitor(*this); } | |
}; | |
class Dog : public Animal { | |
private: | |
virtual void accept(const AnyVisitor& visitor) const { visitor(*this); } | |
}; | |
// A new animal type simply copy/pastes the `accept` function. | |
#include <iostream> | |
#include <memory> | |
#include <vector> | |
struct Move { | |
void operator()(const Cat&) const { | |
std::cout << "Cat move" << std::endl; | |
} | |
void operator()(const Dog&) const { | |
std::cout << "Dog move" << std::endl; | |
} | |
}; | |
struct Meets { | |
void operator()(const Cat&, const Cat&) const { | |
std::cout << "Cat meets Cat" << std::endl; | |
} | |
void operator()(const Cat&, const Dog&) const { | |
std::cout << "Cat meets Dog" << std::endl; | |
} | |
void operator()(const Dog&, const Cat&) const { | |
std::cout << "Dog meets Cat" << std::endl; | |
} | |
void operator()(const Dog&, const Dog&) const { | |
std::cout << "Dog meets Dog" << std::endl; | |
} | |
}; | |
// Generic `Print` that takes one or two animals. | |
// No need to inherit from anything. | |
// You can even have a templated `operator()` for a "catch-all". | |
struct Print { | |
void operator()(const Dog&) const { | |
std::cout << "Dog" << std::endl; | |
} | |
void operator()(const Cat&, const Cat&) const { | |
std::cout << "Cat, Cat" << std::endl; | |
} | |
void operator()(const Dog&, const Dog&) const { | |
std::cout << "Dog, Dog" << std::endl; | |
} | |
template <typename... Animals> | |
void operator()(const Animals&...) const { | |
std::cout << "Don't care" << std::endl; | |
} | |
}; | |
int main() { | |
// Visiting one animal with `Move`. | |
Animal::visit(Move{}, Cat{}); // prints: Cat move | |
Animal::visit(Move{}, Dog{}); // prints: Dog move | |
// Visiting two animals with `Meets`. | |
Animal::visit(Meets{}, Cat{}, Cat{}); // prints: Cat meets Cat | |
Animal::visit(Meets{}, Cat{}, Dog{}); // prints: Cat meets Dog | |
Animal::visit(Meets{}, Dog{}, Cat{}); // prints: Dog meets Cat | |
Animal::visit(Meets{}, Dog{}, Dog{}); // prints: Dog meets Dog | |
// Visiting one or two animals with `Print`. | |
Animal::visit(Print{}, Cat{}); // prints: Don't care | |
Animal::visit(Print{}, Dog{}); // prints: Dog | |
Animal::visit(Print{}, Cat{}, Cat{}); // prints: Cat, Cat | |
Animal::visit(Print{}, Cat{}, Dog{}); // prints: Don't care | |
Animal::visit(Print{}, Dog{}, Cat{}); // prints: Don't care | |
Animal::visit(Print{}, Dog{}, Dog{}); // prints: Dog, Dog | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment