How to Reduce the Code Bloat of a Variadic CRTP
In the previous post we’ve seen how to introduce variadic templates into the CRTP pattern, and how it allowed to create classes with various sets of opt-in features.
For instance, the class X
would have a basic interface but also augment them by inheriting from a set of CRTP base classes:
template<template<typename> typename... Skills> class X : public Skills<X<Skills...>>... { public: void basicMethod() { /*...*/ } };
After a quick recap on the variadic CRTP, we’re going to have a look at the generated type names, and see how to make them shorter if necessary.
An ever-growing template name
The variadic CRTP allows to add extra features that enrich the interface of X
, by using its public interface. Let’s take the example of 4 such extra features:
template<typename Derived> class ExtraFeature1 { public: void extraMethod1() { auto& derived = static_cast<Derived&>(*this); derived.basicMethod(); derived.basicMethod(); derived.basicMethod(); } }; template<typename Derived> class ExtraFeature2 { public: void extraMethod2() { auto& derived = static_cast<Derived&>(*this); // does something else with derived.basicMethod() ... } }; template<typename Derived> class ExtraFeature3 { public: void extraMethod3() { auto& derived = static_cast<Derived&>(*this); // does something else with derived.basicMethod() ... } }; template<typename Derived> class ExtraFeature4 { public: void extraMethod4() { auto& derived = static_cast<Derived&>(*this); // does something else with derived.basicMethod() ... } };
This design allows to tack extra features on X
, with a fairly concise syntax. For example, to add ExtraFeature1
and ExtraFeature4
to the interface of X
, we write:
using X14 = X<ExtraFeature1, ExtraFeature4>;
And we can then call:
X14 x; x.extraMethod1(); x.extraMethod4();
To add all four extra features, we instantiate X
this way:
using X1234 = X<ExtraFeature1, ExtraFeature2, ExtraFeature3, ExtraFeature4>;
Which lets us write the following code:
X1234 x; x.extraMethod1(); x.extraMethod2(); x.extraMethod3(); x.extraMethod4();
X1234
is an alias. But what does its real name look like? Let’s run the program in the debugger, and break the execution into the body of extractMethod1
for example.
Making this experiment in XCode, the top line looks like this:
And if we put each extra feature into its own namespace, the top line of the call stack becomes:
This could be a problem. Beyond the cumbersome symbol in the call stack, large template type names can have a detrimental effect on compilation time and binary size.
It could also be completely OK and unnoticeable. But for the cases where it’s not, let’s see how to keep this template name under control. The idea is to pack all the skills into one class, outside of X
. I learnt about this idea from Nir Friedman on Reddit, and I’m grateful to him for sharing that. Let’s try to implement it.
One skillset instead of a pack of multiple skills
Here is our class X
with the opt-in skills so far:
template<template<typename> typename... Skills> class X : public Skills<X<Skills...>>... { public: void basicMethod() { /*...*/ } };
An instantiation with all 4 extra features looks like this:
using X1234 = X<ExtraFeature1, ExtraFeature2, ExtraFeature3, ExtraFeature4>;
Here are the inheritance relationships in a class diagram:
The types of the extra features are directly connected our class X1234
, and this why they show in its type name.
What about adding an intermediary level, that would know the extra skills? It would be a sort of skill set. And X1234
would only know of this one type, the skillset:
Let’s modify the definition of X
so that it only has one skill (the skillset, that groups them all):
template<template<typename> class SkillSet> class X : public SkillSet<X<SkillSet>> { public: void basicMethod() { /*...*/ } };
Then to define a CRTP skillset, we make it inherit from extra features. For example:
template<typename Derived> class AllFour : public ExtraFeature1<Derived>, public ExtraFeature2<Derived>, public ExtraFeature3<Derived>, public ExtraFeature4<Derived> {};
We use this skillset to instantiate X
:
using X1234 = X<AllFour>; X1234 x; x.extraMethod1(); x.extraMethod2(); x.extraMethod3(); x.extraMethod4();
Let’s now run this code in the debugger, and see what the type name looks like when we break into extraMethod1
:
We now have the name of X1234
under control! Its size no longer depends on the number or complexity of the extra features in the CRTP base classes.
Note how this is a different sort of skillsets than the one we saw in Variadic CRTP Packs: From Opt-in Skills to Opt-in Skillsets. There, the point of grouping skills relating together into skillsets was to make skills more discoverable for a user of X
, and make the definition of X
more concise.
This difference results in a different usage: there, X
could inherit from several skillsets, along with other individual skills. Here, X
inherit from one skillset that we design for it specifically, and that inherits from all the skills (and skillsets) we desire X
to have.
A one line instantiation
The type name is now under control, but the interface is less straightforward to use: we need to create a separate type and then use it:
template<typename Derived> class AllFour : public ExtraFeature1<Derived>, public ExtraFeature2<Derived>, public ExtraFeature3<Derived>, public ExtraFeature4<Derived> {}; using X1234 = X<AllFour>;
Compare this to the original syntax:
using X1234 = X<ExtraFeature1, ExtraFeature2, ExtraFeature3, ExtraFeature4>;
It was more straightforward. But it doesn’t compile any more, because X
now expect only one template parameter, not four.
Could we still define X1234
in one line, for the cases where the size of the generated template name doesn’t matter? Or put another way, can we instantiate a skillset within the definition of X1234
?
Let’s put up the class diagram involving the skillset again:
The skillset is a class template with one parameter (the derived class X
), and that inherits from the extra features. So we would need a function that takes the desired skills, and generate a class template expecting one parameter.
It wouldn’t be a function, but rather a meta-function, as in a function that takes and returns types, not objects. Even more, it would take templates and return templates.
In template meta-programming, meta-functions are represented as template struct
s. Their inputs are their template parameters, and their outputs their nested types. Here we want the template skills as inputs, and the template skillset as outputs.
Let’s call that function make_skills
. A common convention for the output template is to name the corresponding nested template templ
:
template<template<typename> class... Skills> struct make_skills { template<typename Derived> struct templ : Skills<Derived>... { }; };
We can then use it like so:
using X1234 = X<make_skills<ExtraFeature1, ExtraFeature2, ExtraFeature3, ExtraFeature4>::templ>;
But here is what the generated type for X1234 then looks like in the debugger:
Indeed, now X1234
knows again about the skills, because it passes them to the skillset class via make_skills
.
A trade-off
Has decoupling the skillset from X
been an improvement to the design?
It has advantages and drawbacks. Its drawbacks are that make_skills
make an even bigger typename for X
than before we introduced a skillset, however we would use make_skills
for the cases where the type name was not too long anyway. But its code is less direct to instantiate, with the ugly ::templ
sticking out.
But its advantages is that it leaves the flexibility to group all skills into a manually defined skillset, thus keeping the length of the type name under control. But the interface is less straightforward to use, with the separate type to define manually.
Have you used a variadic CRTP? Did you choose to separate the skillset? How did you go about it? Share your experiences, all feedback is welcome.
You may also like
- Variadic CRTP: Opt-in for Class Features, at Compile Time
- Variadic CRTP Packs: From Opt-in Skills to Opt-in Skillsets
Share this post!