Skip to content

Instantly share code, notes, and snippets.

@langthom
Last active December 30, 2017 15:17
Show Gist options
  • Save langthom/b6ec81479f8637a99f81651ac133be7d to your computer and use it in GitHub Desktop.
Save langthom/b6ec81479f8637a99f81651ac133be7d to your computer and use it in GitHub Desktop.
// g++ -Wall -std=c++11 -o crtp CuriouslyRecurringTemplatePattern.cc
// Curiously Recurring Template Pattern (CRTP)
//
// When this is used, a child class inherits from a base class templated with the own class.
#include <iostream>
// Example 1: A Counter for a class instances.
// The base class simply counts the created instances as a static counter.
// Here the template type is important:
// The compiler instantiates one class for each type, therefore not all
// "counted" classes use the same counter, what would be wrong.
template<typename ChildClass>
struct Counter {
static int instancesCreated;
Counter() {
++instancesCreated;
}
};
// Static initialization, as always.
template<typename T> int Counter<T>::instancesCreated = 0;
// Now use it in two classes.
struct ChildOne : Counter<ChildOne> {};
struct ChildTwo : Counter<ChildTwo> {};
//////////////////////////////////////////////////////////////////////////////////////////
// Example 1: A printer.
class InvalidPrinter {
public:
InvalidPrinter(std::ostream& str) : stream(str) {}
template<typename T>
InvalidPrinter& println(T&& instance) {
stream << instance << std::endl;
return *this;
}
protected:
template<typename T>
InvalidPrinter& print(T&& instance) {
stream << instance;
return *this;
}
private:
std::ostream& stream;
};
class InvalidCoutPrinter : public InvalidPrinter {
public:
InvalidCoutPrinter() : InvalidPrinter(std::cout) {}
InvalidCoutPrinter& setColor(const std::string& format) {
print(format);
return *this;
}
};
// correction
template<typename ConcretePrinter>
class CorrectPrinter {
public:
CorrectPrinter(std::ostream& str) : stream(str) {}
template<typename T>
ConcretePrinter& println(T&& instance) {
stream << instance << std::endl;
return static_cast<ConcretePrinter&>(*this);
}
protected:
template<typename T>
ConcretePrinter& print(T&& instance) {
stream << instance;
return static_cast<ConcretePrinter&>(*this);
}
private:
std::ostream& stream;
};
class CorrectCoutPrinter : public CorrectPrinter<CorrectCoutPrinter> {
public:
CorrectCoutPrinter() : CorrectPrinter(std::cout) {}
CorrectCoutPrinter& setColor(const std::string& format) {
print(format);
return *this;
}
};
void testPrinter(void) {
// Using a base printer, everything is fine.
InvalidPrinter{std::cout}.println("Hello World!").println(999);
// Invalid: The inherited println returns a InvalidPrinter, which does not have a 'setColor' member function.
//InvalidCoutPrinter().println("Hello World!").setColor("\033[1;31m").println(999).setColor("\033[0m");
// Now the correct one:
// Now println returns a CorrectCoutPrinter reference, which has the member function.
CorrectCoutPrinter().println("Hello World!").setColor("\033[1;31m").println(999).setColor("\033[0m");
}
//////////////////////////////////////////////////////////////////////////////////////////
// The famous Barton-Nackman trick which uses CRTP and can be used to simulate type classes.
template<typename T>
struct Comparable {
friend bool operator==(const T& lhs, const T& rhs) { return lhs.equals(rhs); }
friend bool operator!=(const T& lhs, const T& rhs) { return !(lhs == rhs); }
};
// The inheritance using CRTP instantiates the Comparable class with our new type.
// This generates the comparison operators with that type automatically, and because
// of the inheritance they become visible and usable.
struct UsesComparable : Comparable<UsesComparable> {
bool equals(const UsesComparable& other) const {
return true;
}
};
//////////////////////////////////////////////////////////////////////////////////////////
// Test code:
int main(void) {
// Create two instances of struct "ChildOne".
struct ChildOne one_first, one_second;
// Create three instances of struct "ChildTwo".
struct ChildTwo two_first, two_second, two_third;
// Now print the counts, which will NOT be 5 as it would be without templates.
std::cout << "Instances of class \"ChildOne\": " << ChildOne::instancesCreated << std::endl;
std::cout << "Instances of class \"ChildTwo\": " << ChildTwo::instancesCreated << std::endl;
std::cout << "Printer tests:" << std::endl;
testPrinter();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment