[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

Previous
Previous

[C++] An Entity-Component-System From Scratch

Next
Next

[C++] Combine Type Erasure And Strategies