[C++] CRTP For Mixin Classes
You can find the coding example here on compiler explorer.
A while ago I published an article about CRTP and C++20 concepts and honestly I really like using concepts to get static polymorphic behaviour. But with concepts this doesnt mean that we don't need CRTP anymore. In Klaus Iglberger's book, C++ Software Design he demonstrates examples for static mixin classes.
Let's Create A Strong Type
First of all we need a strong type. Let's consider following code
namespace cwt {
// a simple class which holds a value of type T
// and uses a type Tag to create more strong_types based on the same type
// you will see below, we'll use meter and kilometer, which are basically both
// integer types.
template<typename T, typename Tag>
class strong_type
{
public:
// a alias to use the the concrete type
using value_type = T;
// an explicit constructor since we have a "strong type"
explicit strong_type(const T& value) : m_value(value) {}
// and a simple access function to retrieve the value
T& get() { return m_value; }
const T& get() const { return m_value; }
private:
T m_value;
};
// we'll use meter and kilometer as example too
template<typename T>
using meter = strong_type<T, struct tag_meter>;
} // namespace cwt
int main()
{
cwt::meter<std::size_t> m1(100);
cwt::meter<std::size_t> m2(50);
m1 + m2; // this doesn't compile
return 0;
}
Now we have a strong type meter
. But, we're pretty limited, as we can't use the +
operator. So it appears to just implement the +
operator to the strong type class. But this will bring a problem, maybe you'll have strong types which I don't want or can't add.
And this is the point where CRTP comes into play. Let's create templated type addable
. Like the name points out, it gives us the ability to add:
// a simple class which only implements the + operator
template<typename T>
struct addable
{
friend T operator+(const T& lhs, const T& rhs)
{
return T(lhs.get() + rhs.get());
}
};
Now we'll need to make a connection to strong_type
. To do so, we'll add variadic template parameters to it. The signature of strong_type
and the meter
alias becomes this:
template<
typename T,
typename Tag,
// we can add multiple skills or abilities to our strong_type
template<typename> typename... Skills
>
// and here crtp will enable via variadic template parameters that we can inherit
// an arbitrary number of types
class strong_type : private Skills<strong_type<T, Tag, Skills...> > ...
{
// ...
};
template<typename T> // we can append our new skill, addable
using meter = strong_type<T, struct tag_meter, addable>;
int main()
{
cwt::meter<std::size_t> m1(100);
cwt::meter<std::size_t> m2(50);
m1 + m2; // and it compiles!
return 0;
}
And this compiles our code without errors. And it's pretty cool because you can add new Skills
very easy. Now think we'd print meter
to stdout
, where I just added std::cout
to our main
. To compile this code, we just need to add a new class printable
and pass it to our meter
alias (no modification in our strong_type
needed):
// our new skill: print our value to stdout
template<typename T>
struct printable
{
friend std::ostream& operator<<(std::ostream& os, const T& t)
{
os << t.get();
return os;
}
};
template<typename T>
// we have 2 skills for meter: addable and printable
using meter = strong_type<T, struct tag_meter, addable, printable>;
// ...
int main()
{
cwt::meter<std::size_t> m1(100);
cwt::meter<std::size_t> m2(50);
// and with printable this prints 150
std::cout << m1 + m2 << std::endl;
return 0;
}
Conclusion
You can find the coding example here on compiler explorer.
I really enjoyed reading this chapter in Klaus Iglberger's book and I look forward to use CRTP mixin classes. If you'd need other types like miles
, kilometers
or anything with a different context, then you can create a new alias for it, add your dedicated Skills
and on clients side it's easy usable.
I hope this helped, but that's it for now.
Best Thomas