Skip to content

Instantly share code, notes, and snippets.

@foonathan
Last active April 5, 2021 01:10
Show Gist options
  • Save foonathan/daad3fffaf5dd7cd7a5bbabd6ccd8c1b to your computer and use it in GitHub Desktop.
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
//=== 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