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
Be the first to start the discussion.