Curiously Recurring Template Pattern(CRTP) in C++ is definitely a powerful technique and a static alternative to virtual functions. But at the same time, learning it may seem a bit weird at first. If you are like me who struggled to grasp anything in one go. Then this article might help you to provide a thought process on where CRTP fits in day-to-day coding. And, if you are an Embedded Programmer, you may run into CRTP more often. Although, std::variant
+ std::visit
will also help but 90% of the compilers for embedded processors are either not up to date with standard or dumb.
There is various material effectively accessible for “How” and “What” on CRTP. So, I will address the “Where” part that CRTP Applicability.
C++
template<typename specific_animal>
struct animal {
void who() { static_cast<specific_animal*>(this)->who(); }
};
struct dog : animal<dog> {
void who() { cout << "dog" << endl; }
};
struct cat : animal<cat> {
void who() { cout << "cat" << endl; }
};
template<typename specific_animal>
void who_am_i(animal<specific_animal> andanimal) {
animal.who();
}
cat c;
who_am_i(c); // prints `cat`
dog d;
who_am_i(d); // prints `dog`
C++
template <class ToBeLimited, uint32_t maxInstance>
struct LimitNoOfInstances {
static atomic<uint32_t> cnt;
LimitNoOfInstances() {
if (cnt >= maxInstance)
throw logic_error{"Too Many Instances"};
++cnt;
}
~LimitNoOfInstances() { --cnt; }
}; // Copy, move & other sanity checks to be complete
struct One : LimitNoOfInstances<One, 1> {};
struct Two : LimitNoOfInstances<Two, 2> {};
template <class T, uint32_t maxNoOfInstace>
atomic<uint32_t> LimitNoOfInstances<T, maxNoOfInstace>::cnt(0);
void use_case() {
Two _2_0, _2_1;
try {
One _1_0, _1_1;
} catch (exception &e) {
cout << e.what() << endl;
}
}
ToBeLimited
, if it isn’t used. In that case, you should have brush up your C++ Template fundamentals or use cppinsights.io. As it isn’t useless.begin()
and end()
. But, the standard library’s requirements for containers require more functionalities like front()
, back()
, size()
, etc.begin()
and end()
in our cases:C++
template <typename T>
class Container {
T &actual() { return *static_cast<T *>(this); }
T const &actual() const { return *static_cast<T const *>(this); }
public:
decltype(auto) front() { return *actual().begin(); }
decltype(auto) back() { return *std::prev(actual().end()); }
decltype(auto) size() const { return std::distance(actual().begin(), actual().end()); }
decltype(auto) operator[](size_t i) { return *std::next(actual().begin(), i); }
};
front()
, back()
, size()
and operator[ ]
for any subclass that has begin()
and end()
.C++
template <typename T>
class DynArray : public Container<DynArray<T>> {
size_t m_size;
unique_ptr<T[]> m_data;
public:
DynArray(size_t s) : m_size{s}, m_data{make_unique<T[]>(s)} {}
T *begin() { return m_data.get(); }
const T *begin() const { return m_data.get(); }
T *end() { return m_data.get() + m_size; }
const T *end() const { return m_data.get() + m_size; }
};
DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
asssert(arr.size() == 10);
#web dev #c++ 11 #crtp #c++ #cplusplus #programming-c