A customer had a header-only design in which the clients of the header are expected to derive from the base class with a specific class name. Sort of like CRTP without the CR. Or the even the T.
// awesome.h
// Client is expected to define this class
// as a derived class of AwesomeAppBase.
struct AwesomeApp;
struct AwesomeAppBase
{
AwesomeApp* derived()
{ return static_cast<AwesomeApp*>(this); }
// AwesomeApp may override this method to provide
// custom preparation work.
void PrepareForSomething() {}
void DoSomething()
{
derived()->PrepareForSomething();
⟦ .. do something ... ⟧
}
};
This doesn’t work because AwesomeÂApp is an incomplete type, so the static_cast<AwesomeApp*>(this) doesn’t know how to convert from an AwesomeÂAppÂBase to an AwesomeÂApp.
Normally, you would fix this problem by deferring the definition of the methods like derived() and DoÂSomething() to a point after AwesomeÂApp has been defined, but this is a header-only library, so there’s no chance to provide code later. The header is your only chance.
We can use a trick we saw earlier: Using a type before it is defined. In this case, we templatize the methods that depend on the definition of AwesomeÂApp by giving them a template type parameter whose default is the incomplete type.
struct AwesomeAppBase
{
template<typename T = AwesomeApp>
AwesomeApp* derived()
{ return static_cast<T*>(this); }
// AwesomeApp may override this method to provide
// custom preparation work.
void PrepareForSomething() {}
template<typename T = AwesomeApp>
void DoSomething()
{
derived<T>()->PrepareForSomething();
⟦ .. do something ... ⟧
}
};
It is, however, rather awkward to have to templatize every method that depends on the AwesomeÂApp. You can remove that awkwardness by templatizing the entire class, transforming it into true CRTP, and then making AwesomeÂAppÂBase be an alias for the version of the template with AwesomeÂApp as the derived class.
template<typename T> struct AwesomeAppBaseT { T* derived() { return static_cast<T*>(this); } // AwesomeApp may override this method to provide // custom preparation work. void PrepareForSomething() {} void DoSomething() { derived()->PrepareForSomething(); ⟦ .. do something ... ⟧ } }; using AwesomeAppBase = AwesomeAppBaseT<AwesomeApp>;
You have to be careful to say T instead of AwesomeÂApp inside the AwesomeÂAppÂBaseÂT so that you use the dependent type and therefore defer the name lookup until the point the template is instantiated.¹
To remove the temptation to say AwesomeÂApp prematurely, you can move the forward declaration of AwesomeÂApp to appear after the template. And you can even use AwesomeÂApp as the name of the template type parameter, so the code looks “normal”.
template<typename AwesomeApp> struct AwesomeAppBaseT { AwesomeApp* derived() { return static_cast<AwesomeApp*>(this); } // AwesomeApp may override this method to provide // custom preparation work. void PrepareForSomething() {} void DoSomething() { derived()->PrepareForSomething(); ⟦ .. do something ... ⟧ } }; // Client is expected to define this class // as a derived class of AwesomeAppBase. struct AwesomeApp; using AwesomeAppBase = AwesomeAppBaseT<AwesomeApp>;
If you don’t want anybody to specialize AwesomeÂAppÂBaseT with anything other than AwesomeÂApp, you can move it into a details namespace. But it seems useful to let people use the full CRTP form, if they want to give their derived class some other name, or if they want to create more than one derived class. (Say, because they want to have a ContosoÂAwesomeÂApp and a FabrikamÂAwesomeÂApp and choose between them at runtime based on a command line switch.)
¹ Note that declaring a type alias does not instantiate the type. According to [temp.inst], instantiation occurs “when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program,” neither of which applies here.
0 comments