Mixin library#

Last Updated on 2023-04-17

The idea with mix-ins is to provide a bunch of primitive classes, where each of them models a basic orthogonal concept, and be able to stick them together to compose more complex classes with just the functionality you want — sort of like Lego. The primitive classes themselves are meant to be used as building blocks. This is extensible since later on you can add other primitive classes to the collection without affecting the existing ones.

A technique for doing this in C++ is using templates and inheritance. The basic idea here is you connect these building blocks together by providing them via the template parameter. You then chain them together, eg. via typedef, to form a new type containing the functionality you want.

What’s a mixin

A mixin is a template struct/class that:

  • inherits publicly from its only template parameter (the CRTP pattern). It is allowed to also inherit from other things,

  • has a constructor that forwards arguments its does not consume to its base.

template<Base>
struct my_mixin : Base { // and any other additional bases

  // A mixin must forward the constructor parameters it does not consume
  // add the parameters to your mixin at the front, before `rest`
  template <typename...Args>
  my_mixin(Args&&... rest) : Base(std::forward<Args>(rest)...) {}

  // public interface methods

  // private state
}

Mixins can have state, which should be implemented with private data members. Access to the state information should be provided via the public interface. That way, mixins are self-contained, their interface is clear and conflicts with other mixins are minimized.

Composing mixins Mixins are added to a class using chain inheritance, where each layer inherits from the layer before it. At the end, the resulting class (which is the one we’ll be using) is the most derived class and provides the interfaces of all mixins used to build it.

One problem remains though: just like that, mixins are restricted on which functionality they can use from other mixins. Interoperability between mixins works only in the direction of the inheritance chain. For example, if we are using a logging mixin with a persistence mixin, then the persistence mixin can use the logging interface as long as it is mixed-in after the logging mixin. But this is not really what we want as conceptually, mixins are a set and we want to be able to compose them in any order we want.

In order to deal with this elegantly, we automatically add a top layer in the chain (not a mixin), that will track the ‘most derived class’ (the one with all the mixins interfaces) and offer access to a self that is the result of casting this to that most derived class. This layer is the initial base in the inheritance chain and is therefore visible from any other mixin.

When defining a concrete class using mixins, you don’t need to do anything to benefit from the above quality of life improvement. Simply declare mixins as usual and access their interfaces via this->self(). We need to have this-> because self() is a dependent name (i.e. depends on the template parameter but is not declared within the template).

template <typename Base> struct WithLogging : Base {
  void Log(const std::string &message) const {
    out << message;
  }
  auto LoggerOutput() -> std::string {
    return out.str();
  }

private:
  mutable std::stringstream out;
};

template <typename Base> struct Persistence : Base {
  void Store() const {
    // self() is provided by Base, so we need to mark it dependent.
    // this->self() is short and obvious.
    this->self().Log("storing...");
  }
};
The order in which you use compose the concrete class using the above mixins is not important, thanks to the top layer and to the trick of providing access to the most derived class via self(). In the example below, the two classes, Concrete and Concrete2, are equivalent:

  struct Concrete1 : Mixin<Concrete1, Persistence, WithLogging> {};
  struct Concrete2 : Mixin<Concrete2, WithLogging, Persistence> {};

The mixin class#

template<typename Concrete, template<class> class ...Mixins>
struct Mixin : public detail::MixinImpl<Concrete, Mixins...>#

Mixes multiple mixins in an inheritance chain starting with a MixinTop and ending with the Concrete class, where each layer inherits from the previous one.

Similar to the rule we set for mixins, where a mixin’s constructor must forward arguments it does not use to its base class, we also require the mixed in class to do the same.

Template Parameters:
  • Concrete – The base class to which mixins will be added.

  • Mixins – The list of mixins to be added to the Concrete base class. Each mixin must respect the following requirements: inherits publicly from its only template parameter and has a constructor that forwards arguments its does not consume to its base.

Public Functions

template<typename ...Rest>
inline explicit constexpr Mixin(Rest&&... rest)#

Constructor that forwards all its arguments to the base class.

Interfaces and mixins#

When it comes to mixing interfaces in a composite class, we have two options:

  1. Implement the interface in a mixin that gets mixed in.

  2. Mix a mixin that provides the interface and implement it in the composite class.

Mixin implements interface#

A mixin must derive from its only template parameter but can also derive from any other base class. This property can be exploited to implement an interface inside a mixin. This is particularly useful when the entire interface makes sense to be implemented in a single place.

Mixin provides interface#

Often, the interface API is too big to be implemented in a single mixin. In such case, we can provide the interface to the composite class via a mixin and implement its API in any suitable way. The challenge with this is the fact that we require a mixin to have a single template parameter. To solve this challenge we use a helper that can curry multiple template parameters and a special mixin Provides that uses the curry helper to provide an interface class to the composite.

template<template<class...> class Mixin, typename ...Args>
struct Curry#

Allows currying of mixin classes with multiple template parameters.

Example

template <typename Arg1, typename Arg2, typename Base> struct MyMixin : Base {
  template <typename... Args>
  constexpr explicit MyMixin(Arg1 field_1, Arg2 field_2, Args &&...args)
      : Base(static_cast<decltype(args)>(args)...), field_1_{field_1},
        field_2_{field_2} {
  }

private:
  Arg1 field_1_;
  Arg2 field_2_;
};

struct MyClass : Mixin<MyClass, mixin::Curry<MyMixin, int, float>::mixin> {
  template <typename... Args>
  constexpr explicit MyClass(Args &&...args)
      : Mixin(static_cast<decltype(args)>(args)...) {
  }
};
template<typename Interface, typename Base = Deferred>
struct Provides : public asap::mixins::Deferred, public Interface#

A mixin that provides an interface to be mixed into the composite class.

Example

struct Interface {
  virtual ~Interface() = default;

  Interface() = default;
  Interface(const Interface &) = default;
  Interface(Interface &&) noexcept = default;
  auto operator=(const Interface &) -> Interface & = default;
  auto operator=(Interface &&) noexcept -> Interface & = default;

  [[maybe_unused]] virtual void foo() = 0;
};
  using asap::mixins::Provides;
  struct Composite final : Mixin<Composite, Provides<Interface>::mixin> {
    void foo() override {
      // Implement foo interface in the composite
    }
  };

Template Parameters:

Interface – - the abstract base class to add to the interface. The rest are implementation details.