Mixin Classes: The Yang of the CRTP

Now that we’re clear on how the CRTP works, let me share with you another technique involving templates that is complementary to the CRTP: Mixin classes. I learnt about mixin classes by watching Arthur O’Dwyer’s Template Normal Programming talk at CppCon (actually you can find them in the slides because they were skipped over during the presentation).

I find mixin classes interesting because they provide another approach to the CRTP to achieve something equivalent, and therefore provide a different trade-off.

Plugging a generic functionality over your type

The main usage of the CRTP is to add a generic functionality to a particular class. Mixin classes do that too.

Mixin classes are template classes that define a generic behaviour, and are designed to inherit from the type you wish to plug their functionality onto.

Here is an example. Let’s take a class representing the name of a person. It has a first name and a last name, and it can print out that name with a specific format:

class Name { public: Name(std::string firstName, std::string lastName) : firstName_(std::move(firstName)) , lastName_(std::move(lastName)) {} void print() const { std::cout << lastName_ << ", " << firstName_ << '

'; } private: std::string firstName_; std::string lastName_; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Name { public : Name ( std :: string firstName , std :: string lastName ) : firstName_ ( std :: move ( firstName ) ) , lastName_ ( std :: move ( lastName ) ) { } void print ( ) const { std :: cout << lastName_ << ", " << firstName_ << '

' ; } private : std :: string firstName_ ; std :: string lastName_ ; } ;

Here is a piece of code using it:

Name ned("Eddard", "Stark"); ned.print(); 1 2 Name ned ( "Eddard" , "Stark" ) ; ned . print ( ) ;

which outputs:

Stark, Eddard 1 Stark , Eddard

Nothing spectacular so far, but here is a new requirement: we need to be able to print this name several times in a row.

We could add a repeat method to the Name class. But the concept of repeatedly call the print method is something that could apply to other classes, like a PhoneNumber class that could also have a print() method.

The idea of the mixin class is to isolate the generic functionality into its own class, template this class on the type we want to plug in onto, and derive from that type:

template<typename Printable> struct RepeatPrint : Printable { explicit RepeatPrint(Printable const& printable) : Printable(printable) {} void repeat(unsigned int n) const { while (n-- > 0) { this->print(); } } }; 1 2 3 4 5 6 7 8 9 10 11 12 template < typename Printable > struct RepeatPrint : Printable { explicit RepeatPrint ( Printable const & printable ) : Printable ( printable ) { } void repeat ( unsigned int n ) const { while ( n -- > 0 ) { this -> print ( ) ; } } } ;

In our example the Name class will play the role of Printable .

Note the this-> in the implementation of the repeat method. Without it the code would not compile. Indeed, the compiler is not sure where print is declared: even if it is declared in the template class Printable , in theory nothing guarantees that this template class won’t be specialized and rewritten on a particular type, that would not expose an print method. For that reason, names in template base classes are ignored in C++.

Using this-> is a way to include them back in the scope of functions considered to resolve the call. There are other ways to do it, although they are arguably not as adapted to this situation. In any case, you can read all about this topic in Effective C++ Item 43.

To avoid specifying template arguments explicitly we use a function that deduces them:

template<typename Printable> RepeatPrint<Printable> repeatPrint(Printable const& printable) { return RepeatPrint<Printable>(printable); } 1 2 3 4 5 template < typename Printable > RepeatPrint < Printable > repeatPrint ( Printable const & printable ) { return RepeatPrint < Printable > ( printable ) ; }

And here is the client code:

Name ned("Eddard", "Stark"); repeatPrint(ned).repeat(10); 1 2 Name ned ( "Eddard" , "Stark" ) ; repeatPrint ( ned ) . repeat ( 10 ) ;

which outputs:

Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard Stark, Eddard 1 2 3 4 5 6 7 8 9 10 Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard Stark , Eddard

We can even change the names to get to code even more expressive:

Name ned("Eddard", "Stark"); repeatedlyPrint(ned).times(10); 1 2 Name ned ( "Eddard" , "Stark" ) ; repeatedlyPrint ( ned ) . times ( 10 ) ;

(I’m changing the names only now in order to compare the previous code with the CRTP, for which these new names are not adapted.)

The CRTP upside down

Mixin classes involve a mix of template and inheritance in order to plug a generic functionality onto an existing class. This kind of feels like the CRTP, doesn’t it?

Mixin classes are like the CRTP, but upside down. Indeed our mixin class looks like this:

class Name { ... }; template<typename Printable> struct RepeatPrint : Printable { ... }; repeatPrint(ned).repeat(10); 1 2 3 4 5 6 7 8 9 10 11 class Name { . . . } ; template < typename Printable > struct RepeatPrint : Printable { . . . } ; repeatPrint ( ned ) . repeat ( 10 ) ;

while the corresponding CRTP would rather look like this:

template<typename Printable> struct RepeatPrint { ... }; class Name : public RepeatPrint<Name> { ... }; ned.repeat(10); 1 2 3 4 5 6 7 8 9 10 11 12 template < typename Printable > struct RepeatPrint { . . . } ; class Name : public RepeatPrint < Name > { . . . } ; ned . repeat ( 10 ) ;

In fact, here is the whole implementation of the solution using the CRTP:

template<typename Printable> struct RepeatPrint { void repeat(unsigned int n) const { while (n-- > 0) { static_cast<Printable const&>(*this).print(); } } }; class Name : public RepeatPrint<Name> { public: Name(std::string firstName, std::string lastName) : firstName_(std::move(firstName)) , lastName_(std::move(lastName)) {} void print() const { std::cout << lastName_ << ", " << firstName_ << '

'; } private: std::string firstName_; std::string lastName_; }; int main() { Name ned("Eddard", "Stark"); ned.repeat(10); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 template < typename Printable > struct RepeatPrint { void repeat ( unsigned int n ) const { while ( n -- > 0 ) { static_cast < Printable const & > ( * this ) . print ( ) ; } } } ; class Name : public RepeatPrint < Name > { public : Name ( std :: string firstName , std :: string lastName ) : firstName_ ( std :: move ( firstName ) ) , lastName_ ( std :: move ( lastName ) ) { } void print ( ) const { std :: cout << lastName_ << ", " << firstName_ << '

' ; } private : std :: string firstName_ ; std :: string lastName_ ; } ; int main ( ) { Name ned ( "Eddard" , "Stark" ) ; ned . repeat ( 10 ) ; }

So, CRTP or mixin class?

CRTP and mixin classes provide two approaches to the same problem: adding a generic functionality to an existing class, but with different trade-offs.

Here are the points where they differ:

The CRTP:

impacts the definition of the existing class, because it has to inherit from the CRTP,

client code uses the original class directly and benefits from its augmented functionalities.

The mixin class:

leaves the original class unchanged,

client code doesn’t use the original class directly, it needs to wrap it into the mixin to use the augmented functionality,

inherits from a the original class even if it doesn’t have a virtual destructor. This is ok unless the mixin class is deleted polymorphically through a pointer to the original class.

Understanding these trade-off lets you choose the solution that fits best to a given situation.

There is more than that to the CRTP. If you want to know more about it, I’ve dedicated a whole series of posts to the CRTP, which has become quite popular.

Special thanks to Arthur for his talk, and also for taking the time to help me understand mixin classes.

Related articles:

Share this post! Don't want to miss out ?