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:

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:

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:

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:

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:

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:

template<typename ...Hosts>
struct Visits : public atoms::detail::Visits<Hosts...>

Visitor type for given set of derived classes (Hosts).

Typical use case is using MyTypeVisitor = atoms::Visits< Derived1, Derived2 >

Subclassed by UniversalModuleJointVisitor

Public Static Functions

template<typename ...Fs>
static inline auto make(Fs... fs)

Build a visitor out of collables (functors, free functions)

It expects that all callables have the same return value and each of the accepts exactly one overload of the base class.

template<typename Self, typename Visitor>
struct VisitableBase

Make a base class visitable.

This is a CRTP class, inherit from it in your base class and pass the base class type and a base visitor type as template arguments. The base visitor type should be created as a using to Visits.

Typical use case class Base: public atoms::VisitableBase< Base, BaseVisitor > {};

Public Types

using VisitorType = Visitor

Public Functions

virtual ~VisitableBase() = default
inline virtual void accept(VisitorType&)
template<typename Base, typename Self>
struct Visitable : public Base

Implement visits interface in derived class.

This is a CRTP class, inherit from it in you class and pass base class and your derived class as the template arguments.

Typical use case class Derived: public Visitable< Base, Derived > {};

Public Functions

template<typename ...Ts>
inline explicit Visitable(Ts&&... ts)
inline void accept(typename Base::VisitorType &visitor) override
template<typename Base, typename F, typename ...Fs>
struct Visitor : public atoms::detail::Visitor<Base, FunctionTraits<F>::returnType, F, Fs...>

This type takes a desired visitor type (created by templateing Visits), list of callables and produces a visitor out of the callable.

Prefer calling Visits::make() for creating instances as it automatically deduces all the types.

Public Types

using ReturnType = typename FunctionTraits<F>::returnType
using Successor = detail::Visitor<Base, ReturnType, F, Fs...>

Public Functions

inline explicit Visitor(F f, Fs... fs)
template<typename T, typename ...Fs>
auto atoms::visit(T &object, Fs... fs)

Visit given object by se of callables.

Expects that T inherits from atoms::VisitableBase.

Expects that all callables have the same return value and each of them accepts exactly one overload of T.

template<typename T, typename Visitor>
auto atoms::visit(T &object, Visitor visitor) -> std::enable_if_t<std::is_base_of_v<typename T::VisitorType, Visitor>, typename Visitor::ReturnType>

Visit given object by a visitor.

Gives visiting the same interface as std::visits provides and removes the hassle of extracting the return value out of the visitor.

Expects that T inherits from atoms::VisitableBase.

Expects that Visitor::ReturnType and Visitor::result (in case of non-void return type) exists. Both of these are guaranteed if atoms::Visitor is used.

Cloneable Object Hiearchy & ValuePtr

Atoms provide means to simply implement cloneable virtual hiearchies. Consider the following code:

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.

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 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.

template<typename T>
class ValuePtr

Store object on a heap (to allow virtual function calls and to provide stable address on move) and provide deep copies.

It requires that T provides T *clone() const. Use ATOMS_CLONEABLE_BASE and ATOMS_CLONEABLE for providing this method.

Public Functions

ValuePtr() = default
inline explicit ValuePtr(std::unique_ptr<T> ptr)
inline explicit ValuePtr(const T &t)
template<std::derived_from<T> Derived>
inline explicit ValuePtr(std::unique_ptr<T> ptr)
ValuePtr(ValuePtr&&) noexcept = default
ValuePtr &operator=(ValuePtr&&) noexcept = default
inline ValuePtr clone() const
inline void swap(ValuePtr &other) noexcept
inline T &operator*() const noexcept
inline T *operator->() const noexcept
inline T *get() const noexcept
inline explicit operator bool() const noexcept