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 AwesomeApp is an incomplete type, so the static_cast<AwesomeApp*>(this) doesn’t know how to convert from an AwesomeAppBase to an AwesomeApp.
Normally, you would fix this problem by deferring the definition of the methods like derived() and DoSomething() to a point after AwesomeApp 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 AwesomeApp 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 AwesomeApp. You can remove that awkwardness by templatizing the entire class, transforming it into true CRTP, and then making AwesomeAppBase be an alias for the version of the template with AwesomeApp 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 AwesomeApp inside the AwesomeAppBaseT 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 AwesomeApp prematurely, you can move the forward declaration of AwesomeApp to appear after the template. And you can even use AwesomeApp 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 AwesomeAppBaseT with anything other than AwesomeApp, 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 ContosoAwesomeApp and a FabrikamAwesomeApp 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