Patterns ======== Patterns aim for removing unnecessary boilerplate code from your project for commonly used programming patterns. Visitor Pattern --------------- Visitors allow you to separate algorithms from your data structures in object hierarchy. It allows you to implement new functionality for existing classes without modifying them (i.e., adding new virtual methods). See `Wikipedia `_ for more details. To remove boilerplate, you can use Atoms. Consider a simple class hiearchy of animals: .. code-block:: cpp class Animal { public: virtual ~Animal() = default; }; class Cat: public Animal {}; class Dog: public Animal {}; To make them visitable via atoms, you need to modify your definition a little bit: .. code-block:: cpp class Cat; // Forward declaration, so we can define Visitor class Dog; // Forward declaration, so we can define Visitor // Our visitor base class has to be defined before the base class, so we can // specify the visitor type in the definition using AnimalVisitor = atoms::Visits< Cat, Dog >; class Animal: public atoms::VisitableBase< Animal, AnimalVisitor > { public: virtual ~Animal() = default; }; class Cat: public atoms::Visitable< Animal, Cat > {}; // Make cat visitable class Dog: public atoms::Visitable< Animal, Dog > {}; // Make dog visitable Now you can hand-craft a traditional visitor and use it: .. code-block:: cpp class TypeVisitor: public AnimalVisitor { public: void operator()( Cat& a ) override { result = "cat"; } void operator()( Dog& b ) override { result = "dog"; } std::string result; }; // ...and when you want to use it: Cat cat; Dog dog; Animal *hiddenCat = &cat; Animal *hiddenDog = &dog; TypeVisitor visitor; cat.accept( visitor ); std::cout << visitor.result << "\n"; dog.accept( visitor ); std::cout << visitor.result << "\n"; hiddenCat->accept( visitor ); std::cout << visitor.result << "\n"; hiddenDog->accept( visitor ); std::cout << visitor.result << "\n"; With atoms, you can even construct a visitor in place from an ordinary function or/and lambdas: .. code-block:: cpp std::string catType( Cat& ) { return "cat"; } auto visitor = AnimalVisitor::make( catType, // Cat is visited by an ordinary function []( Dog& ) -> std::string { return "dog" }); // Dog is visited by a lambda // Note that you have to cover all cases, otherwise your program won't compile // Then we can use it like before hiddenCat->accept( visitor ); std::cout << visitor.result << "\n"; This is not all, with Atoms, you can avoid the annoying "accept then result" construction and visit object like function call: .. code-block:: cpp std::cout << atoms::visit( hiddenCat, visitor) << "\n"; // You can even define the visitor in place: std::cout << atoms::visit( hiddenCat, []( Cat& cat ) { return "cat"; }, []( Dog& dog ) { return "dog"; } ) << "\n"; Bellow, you can find the references of the provided classes and functions: .. doxygenstruct:: atoms::Visits :project: lib .. doxygenstruct:: atoms::VisitableBase :project: lib .. doxygenstruct:: atoms::Visitable :project: lib .. doxygenstruct:: atoms::Visitor :project: lib .. doxygenfunction:: atoms::visit(T &object, Fs... fs) :project: lib .. doxygenfunction:: atoms::visit(T &object, Visitor visitor) -> std::enable_if_t, typename Visitor::ReturnType> :project: lib Cloneable Object Hiearchy & ValuePtr ------------------------------------ Atoms provide means to simply implement cloneable virtual hiearchies. Consider the following code: .. code-block:: cpp class Animal { public: virtual ~Animal() = default; }; class Dog: public Animal {}; class Cat: public Animal {}; // Our code: std::unique_ptr< Animal > ptr( new Dog() ); // Let's make a copy - how to do so? We don't know what is behind Animal. // The following attempt won't work: std::unique_ptr< Animal > ptrCopy( new Animal( *ptr.get() ) ); // Does not compile To make the it work we can add a virtual ``clone`` method. This process can be tedious, so Atoms provide several macros to easy the task. By the way, we use macro-based approach over CRTP-based approach as we consider it more readable and easier to maintain. .. code-block:: cpp class Animal { public: virtual ~Animal() = default; ATOMS_CLONEABLE_BASE( Animal ); }; class Dog: public Animal { public: ATOMS_CLONEABLE( Dog ); }; class Cat: public Animal { public: ATOMS_CLONEABLE( Cat ); }; // Our code: std::unique_ptr< Animal > ptr( new Dog() ); // Let's make a copy: std::unique_ptr< Animal > ptrCopy( ptr->clone() ); Sometimes, you need to store a such hierarchy in class which you want to make copyable. With ``unique_ptr`` you have to manually define all the copy and move constructors. This can be tedious, therefore, atoms provide :cpp:class:`atoms::ValuePtr` which stores objects on heap, but provides copy semantics for them. It is somewhat similar to proposed ``std::polymorphic_value``, however, it does not feature small-buffer optimization to provide stable references. .. doxygenclass:: atoms::ValuePtr :project: lib