class NetworkServiceConfig : public Config<NetworkServiceConfig>{ ...
Wait! What?
ok ok … this is not a joke. In fact this is design pattern, and it is called Curiously Recurring Template Pattern CTPR. 🙂 So why anyone would like to do that? Why to derive from a template that takes a Derived class as a template parameter ? And how this is not making compiler crash ?
CRTP
In general CTPR allows your base class to access your derived class content without cost of a dynamic casting. Sounds strange isn’t it. Lets check this on a example. One of a CRTP use cases is “polymorphic copy-construction” (when you want to copy object via base pointer).
#pragma once
#include <memory>
class AbsConfig{
public:
virtual ~AbsConfig () = default;
virtual std::unique_ptr<AbsConfig> clone() const = 0;
};
// This CRTP class implements clone() for Derived
template <typename Derived>
class Config : public AbsConfig {
public:
std::unique_ptr<AbsConfig> clone() const override {
return std::make_unique<Derived>(static_cast<Derived const&>(*this));
}
~Config() = default;
protected:
Config() = default;
Config(const Config&) = default;
Config(Config&&) = default;
};
So above template is declaring virtual clone() function for every class derived from this template, that is based on a AbsConfig class and derived class type is passed as template parameter. In particular base class will take advantage of fact that member function bodies (definitions) are not instantiated until long after their declarations, and will use members of the derived class within its own member functions, via the use of a cast. (I really tried to write this simpler, but it is best I can do, this is a bit like “to understand recursion you need to understand recursion”. Just read couple of times and analyse the code example. Sorry.) Back to the example. You co not need to repeat declaring clone() function in every derived class. It works like this …
#include <abs_config.h>
#include <string>
class NetworkServiceConfig : public Config<NetworkServiceConfig>{
public:
std::string username;
std::string password;
std::string address;
NetworkServiceConfig(const std::string& username, const std::string& password, const std::string& address);
};
and usage :
int main (int argc, char* argv[]){
auto config = std::make_unique<NetworkServiceConfig>("user", "password", "localhost");
auto copy = config->clone();
//...
}
Foot note
It is not a point to create “curiously looking code“, the point is that to decide that something is good readable code or not we need to know language, and learn common design patterns. When I first encountered CRTP it was commented “this is CRTP, RTFB”. 🙂 I think that book that RTFB was referring is “Generic Programming and Design Patterns Applied” by Andrei Alexsandrescu.
For compilable code example please go to my github.
With best pattern regards
Michał