Can partial template specializations be combined to produce implicitly generated shared code paths?

Viewed 68

I was thinking about about template specialization and was wondering if there was a way to use partial specialization to generate two different code paths that are automatically merged.

In this case I have a computational engine where I want an enum to select different functions at compile-time. In this case, depending on the policy or scheme I want different functions at compile-time.

I was thinking that I could avoid explicitly partially specializing all variants. Is this possible?

I've included a small code sample.

#include <iostream>

enum class scheme { linear, polynomial };

enum class policy { no_checking, raise_exception };

struct computational_base
{
    void left();
    void middle();
    void do_stuff()
    {
        left();
        middle();
    }
};

template <scheme scheme, policy left>
struct computational_backend : public computational_base
{

};

template <policy left>
struct computational_backend<scheme::linear, left> : public computational_base
{
    void middle()
    {
        std::cout << "scheme::linear" << std::endl;
    }
};

template <scheme scheme>
struct computational_backend<scheme, policy::no_checking> : public computational_base
{
    void left()
    {
        std::cout << "policy::no_checking" << std::endl;
    }
};

int main()
{
    //Ideally would select middle() from first template, and left() from second template
    // more than one partial specialization matches the template argument list
    computational_backend<scheme::linear, policy::no_checking> what;
    what.do_stuff();
    return 0;
}
1 Answers

CRTP and multiple inheritance can be your friend. At the core, you can use multiple inheritence to bring functionality in from both classes

template <scheme s, policy p>
struct computational_backend
: scheme_backend<s>
, policy_backend<p>
{ };

This sort of thing will work, as long as the different parts of the computational process don't need to call eachother. In other words, it works when middle() never needs to call left(), and vice versa.

If you need them to call each other, the Curiously Recursive Template Pattern (CRTP) is your friend. This is an oddity which basically lets you cast to the most derived type from the base classes because you pass that most derived type in as a template parameter. It looks like this:

template <typename DerivedT, scheme s>
struct scheme_backend;

template <typename DerivedT, policy p>
struct policy_backend;

template <typename DerivedT>
struct scheme_backend<DerivedT, scheme::linear>
{
    DerivedT& derived()
    {
        return *static_cast<DerivedT*>(this);
    }

    void left()
    {
        ...
        derived().middle();
        ...
    }
};

template <scheme s, policy p>
struct computational_backend
: scheme_backend<computational_backend<s, p>, s>
, policy_backend<computational_backend<s, p>, p>
{ };

I only drew one of the specializations, but you get the idea. It turns out that strange static cast is not only legal in C++, it is, in fact, extraordinarily fast. In many cases, the compiler can optimize it away completly.

Related