Mixins via CRTP
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 77 of 104.
The Curiously Recurring Template Pattern (CRTP) is a technique where a class inherits from a template base class, passing itself as the template argument. This enables compile-time polymorphism and allows base classes to access derived class members without virtual functions.
The basic CRTP structure looks like this:
template <typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class MyClass : public Base<MyClass> {
public:
void implementation() {
std::cout << "MyClass implementation\n";
}
};CRTP is particularly powerful for creating mixins - reusable functionality that can be "mixed into" classes. Unlike traditional inheritance, mixins add capabilities without creating deep hierarchies:
template <typename Derived>
class Printable {
public:
void print() const {
const Derived& self = static_cast<const Derived&>(*this);
std::cout << self.toString() << "\n";
}
};
class Person : public Printable<Person> {
std::string name;
public:
Person(const std::string& n) : name(n) {}
std::string toString() const { return "Person: " + name; }
};
// Usage:
Person p("Alice");
p.print(); // Output: Person: AliceThe key advantage is that all method calls are resolved at compile time, eliminating virtual function overhead. You can combine multiple CRTP mixins to compose functionality, making it a flexible alternative to runtime polymorphism when the types are known at compile time.
Challenge
EasyLet's build a logging system that uses CRTP mixins to add reusable functionality to different classes without virtual function overhead. You'll create two mixins that can be "mixed into" any class: one for counting instances and one for generating string representations.
You'll organize your code across three files:
Mixins.h: Define two CRTP mixin templates that provide reusable functionality.Create a
Countablemixin template that tracks how many instances of a derived class exist. It should have a static counter that increments in the constructor and decrements in the destructor. Provide a static methodgetCount()that returns the current count.Create a
Describablemixin template that provides adescribe()method. This method should usestatic_castto access the derived class and call itsgetDescription()method, then print the result followed by a newline.Remember that CRTP mixins use
static_cast<Derived*>(this)to access the derived class's members at compile time.Entities.h: Define two entity classes that inherit from both mixins.Create a
Playerclass that inherits from bothCountable<Player>andDescribable<Player>. It should store a name and level, and provide agetDescription()method that returns a string in the format:Player: [name] (Level [level])Create an
Enemyclass that also inherits from both mixins. It should store a type and health, and provide agetDescription()method that returns:Enemy: [type] with [health] HPDon't forget to initialize the static counter for each class.
main.cpp: Read four inputs (each on a separate line):- Player name (string)
- Player level (integer)
- Enemy type (string)
- Enemy health (integer)
Create a Player and an Enemy with the given values. Then demonstrate the mixins:
- Print
Player count: [count]using the staticgetCount()method - Print
Enemy count: [count] - Call
describe()on the player - Call
describe()on the enemy - Create a second player with name "Guest" and level 1
- Print
Player count: [count]again to show the updated count - Call
describe()on the second player
For example, with inputs Hero, 10, Dragon, and 500:
Player count: 1
Enemy count: 1
Player: Hero (Level 10)
Enemy: Dragon with 500 HP
Player count: 2
Player: Guest (Level 1)This challenge demonstrates how CRTP mixins add functionality (counting and describing) to unrelated classes without using virtual functions. Both Player and Enemy gain the same capabilities by inheriting from the same mixin templates, but each maintains its own separate instance counter because the template is instantiated with different types.
Cheat sheet
The Curiously Recurring Template Pattern (CRTP) enables compile-time polymorphism by having a class inherit from a template base class, passing itself as the template argument:
template <typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class MyClass : public Base<MyClass> {
public:
void implementation() {
std::cout << "MyClass implementation\n";
}
};CRTP Mixins provide reusable functionality without deep inheritance hierarchies:
template <typename Derived>
class Printable {
public:
void print() const {
const Derived& self = static_cast<const Derived&>(*this);
std::cout << self.toString() << "\n";
}
};
class Person : public Printable<Person> {
std::string name;
public:
Person(const std::string& n) : name(n) {}
std::string toString() const { return "Person: " + name; }
};Key advantages:
- All method calls resolved at compile time
- No virtual function overhead
- Multiple mixins can be combined to compose functionality
- Each template instantiation maintains separate state (e.g., separate static counters per derived type)
Try it yourself
#include <iostream>
#include <string>
#include "Entities.h"
using namespace std;
int main() {
// Read inputs
string playerName;
int playerLevel;
string enemyType;
int enemyHealth;
cin >> playerName;
cin >> playerLevel;
cin >> enemyType;
cin >> enemyHealth;
// TODO: Create a Player with the given name and level
// TODO: Create an Enemy with the given type and health
// TODO: Print "Player count: [count]" using Player::getCount()
// TODO: Print "Enemy count: [count]" using Enemy::getCount()
// TODO: Call describe() on the player
// TODO: Call describe() on the enemy
// TODO: Create a second player with name "Guest" and level 1
// TODO: Print "Player count: [count]" again
// TODO: Call describe() on the second player
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container