Skip to content

πŸ§‘β€πŸ€β€πŸ§‘ The visitor pattern revisited. An inheritance-aware acyclic visitor template, any and any-function templates.

Notifications You must be signed in to change notification settings

TheLartians/Revisited

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Actions Status Actions Status Actions Status Actions Status Actions Status codecov

Revisited

A C++17 acyclic visitor template and inheritance-aware any and any-function class. Using revisited::Visitor greatly reduces the boilerplate code required for implementing the visitor pattern in C++. It uses only compile time type information and has better performance than solutions relying on run time type information such as dynamic_cast.

Examples

See the examples directory for full examples.

Revisited Examples

Simple Visitor

#include <memory>
#include <iostream>
#include <revisited/visitor.h>

struct Base: public virtual revisited::VisitableBase { };
struct A: public Base, public revisited::Visitable<A> { };
struct B: public Base, public revisited::Visitable<B> { };

struct Visitor: public revisited::Visitor<A &,B &> {
  void visit(A &){ std::cout << "Visiting A" << std::endl; }
  void visit(B &){ std::cout << "Visiting B" << std::endl; }
};

int main() {
  std::shared_ptr<Base> a = std::make_shared<A>();
  std::shared_ptr<Base> b = std::make_shared<B>();
  
  Visitor visitor;
  a->accept(visitor); // -> Visiting A
  b->accept(visitor); // -> Visiting B
}

Derived Classes

revisited::Visitor also understands derived classes and classes with multiple visitable base classes. Virtual visitable base classes are also supported. When visiting a derived object, the first class matching the visitor is used (starting from parent classes). Multiple and virtual inheritance is fully supported.

// C is inherited from A (both can be visited)
struct C: public revisited::DerivedVisitable<C, A> { };
// D is inherited from A and B (A and B can be visited)
struct D: public revisited::JoinVisitable<A, B> { };
// E is virtually inherited from  A and B (E, A and B can be visited)
struct E: public revisited::DerivedVisitable<E, revisited::VirtualVisitable<A, B>> { };

revisited::Any Examples

Implicit casting

revisited::Any v;
v = 42;
std::cout << v.get<int>() << std::endl; // -> 42
std::cout << v.get<double>() << std::endl; // -> 42
v = "Hello Any!";
std::cout << v.get<std::string>() << std::endl; // -> Hello Any!

Reference aware casting

int x = 42;
revisited::Any a = std::reference_wrapper(x);
std::cout << a.get<double>() << std::endl; // -> 42
std::cout << &a.get<int&>() == &x << std::endl; // -> 1

Inheritance aware casting

// inheritance aware
struct MyClassBase{ int value; };
struct MyClass: public MyClassBase{ MyClass(int value):MyClassBase{value}{ } };
revisited::Any v;
v.setWithBases<MyClass, MyClassBase>(42);
std::cout << v.get<MyClassBase &>().value << std::endl; // -> 42
std::cout << v.get<MyClass &>().value << std::endl; // -> 42

revisited::AnyFunction Examples

revisited::AnyFunction f;
f = [](int x, float y){ return x + y; };
std::cout << f(40,2).get<int>() << std::endl; // -> 42

Installation and usage

With CPM, revisited::Visitor can be used in a CMake project simply by adding the following to the project's CMakeLists.txt.

CPMAddPackage(
  NAME Revisited
  GIT_REPOSITORY https://github.com/TheLartians/Visitor.git
  VERSION 2.0
)

target_link_libraries(myProject Revisited)

Alternatively, the repository can be cloned locally and included it via add_subdirectory. Installing revisited::Visitor will make it findable in CMake's find_package.

Performance

revisited::Visitor uses meta-programming to determine the inheritance hierarchy at compile-time for optimal performance. Compared to the traditional visitor pattern revisited::Visitor requires an additional virtual calls (as the type of the visitor and the visitable object are unknown). With compiler optimizations enabled, these calls should be hardly noticeable in real-world applications.

There is an benchmark suite included in the repository that compares the pure cost of the different approaches.

cmake -Hbenchmark -Bbuild/bench -DCMAKE_BUILD_TYPE=Release
cmake --build build/bench -j8
./build/bench/RevisitedBenchmark