Last active
April 5, 2021 01:10
-
-
Save foonathan/daad3fffaf5dd7cd7a5bbabd6ccd8c1b to your computer and use it in GitHub Desktop.
My take on a modern implementation of the visitor pattern. - http://foonathan.net/blog/2017/12/21/visitors.html
This file contains hidden or 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
//=== library ===// | |
#include <typeinfo> | |
#include <type_traits> | |
#include <utility> | |
enum class visit_event | |
{ | |
container_begin, | |
container_end, | |
leaf, | |
}; | |
class base_visitor; | |
class container_visitable | |
{ | |
protected: | |
~container_visitable() = default; | |
private: | |
virtual bool is_container() const { return true; } | |
virtual void visit_children(base_visitor& visitor) const = 0; | |
friend base_visitor; | |
}; | |
template <typename T> | |
const void* get_most_derived(const T& obj) | |
{ | |
if constexpr (!std::is_polymorphic_v<T> || std::is_final_v<T>) | |
return &obj; | |
else | |
return dynamic_cast<const void*>(&obj); | |
} | |
class base_visitor | |
{ | |
public: | |
template <typename T> | |
void operator()(const T& obj) | |
{ | |
if constexpr (std::is_base_of_v<container_visitable, T>) | |
{ | |
if (static_cast<const container_visitable&>(obj).is_container()) | |
{ | |
do_visit(visit_event::container_begin, get_most_derived(obj), typeid(obj)); | |
static_cast<const container_visitable&>(obj).visit_children(*this); | |
do_visit(visit_event::container_end, get_most_derived(obj), typeid(obj)); | |
} | |
else | |
do_visit(visit_event::leaf, get_most_derived(obj), typeid(obj)); | |
} | |
else | |
do_visit(visit_event::leaf, get_most_derived(obj), typeid(obj)); | |
} | |
protected: | |
~base_visitor() {} | |
private: | |
virtual void do_visit(visit_event ev, const void* ptr, const std::type_info& type) = 0; | |
}; | |
template <typename Function, typename ... Types> | |
class lambda_visitor : public base_visitor | |
{ | |
public: | |
explicit lambda_visitor(Function f) | |
: f_(std::move(f)) {} | |
private: | |
template <typename T> | |
bool try_visit(visit_event ev, const void* ptr, const std::type_info& type) | |
{ | |
if (type == typeid(T)) | |
{ | |
f_(ev, *static_cast<const T*>(ptr)); | |
return true; | |
} | |
else | |
return false; | |
} | |
void do_visit(visit_event ev, const void* ptr, const std::type_info& type) override | |
{ | |
(try_visit<Types>(ev, ptr, type) || ...); | |
} | |
Function f_; | |
}; | |
template <typename... Functions> | |
auto overload(Functions... functions) | |
{ | |
struct lambda : Functions... | |
{ | |
lambda(Functions... functions) : Functions(std::move(functions))... {} | |
using Functions::operator()...; | |
}; | |
return lambda(std::move(functions)...); | |
} | |
template <typename ... Types> | |
struct type_list {}; | |
template <typename ... Types, typename ... Functions> | |
auto make_visitor(type_list<Types...>, Functions... funcs) | |
{ | |
auto overloaded = overload(std::move(funcs)...); | |
return lambda_visitor<decltype(overloaded), Types...>(std::move(overloaded)); | |
} | |
//=== example ===// | |
#include <memory> | |
#include <iostream> | |
#include <vector> | |
class node : public container_visitable | |
{ | |
public: | |
virtual ~node() = 0; | |
protected: | |
// treat all as non-container for simplicity | |
bool is_container() const override { return false; } | |
void visit_children(base_visitor&) const override {} | |
}; | |
node::~node() {} | |
class document final : public node | |
{ | |
public: | |
template <typename ... Children> | |
explicit document(Children... children) | |
{ | |
// stupid initializer list can't move... | |
std::unique_ptr<node> tmp[] = {std::move(children)...}; | |
children_.reserve(sizeof...(Children)); | |
for (auto& el : tmp) | |
children_.push_back(std::move(el)); | |
} | |
private: | |
bool is_container() const override { return true; } | |
void visit_children(base_visitor& visitor) const override | |
{ | |
for (auto& child : children_) | |
visitor(*child); | |
} | |
std::vector<std::unique_ptr<node>> children_; | |
}; | |
class text final : public node {}; | |
int main() | |
{ | |
auto doc_child = std::make_unique<document>(std::make_unique<text>(), | |
std::make_unique<text>()); | |
document doc(std::move(doc_child), std::make_unique<text>()); | |
auto visitor = make_visitor(type_list<document, text, int, double>{}, | |
[&](visit_event, const document&) | |
{ | |
std::cout << "doc\n"; | |
}, | |
[&](visit_event, const text&) | |
{ | |
std::cout << "text\n"; | |
}, | |
[&](visit_event, int) | |
{ | |
std::cout << "int\n"; | |
}); | |
visitor(doc); | |
visitor(0); // prints int | |
visitor(3.14); // prints int as well as double is converted | |
visitor("hello"); // type ignored | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment