Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template specialization for subclasses of template base class

This problem is a little hard to explain, so I will start with an example:

I have a class template that takes a type and an integer constant as template parameters, and I have a number of child classes that derive from instantiations of that template:

template <class V, int i>
struct Base 
{
    static void doSomething() { cout << "something " << i << endl; };
};

struct Child : public Base<int,12>
{
};

I want to use these classes with some other template (let's call it Test), which has specializations for different types. Because the behaviour should be exactly the same for all classes that are derived from any instantiation of Base, I want to define just a single specialization of Test that handles all classes derived from Base.

I know that I cannot directly specialize for Base<V,i> because this would not detect the child classes. Instead, my first approach was using Boost's enable_if and type traits:

// empty body to trigger compiler error for unsupported types
template <class T, class Enabled = void>
struct Test { };

// specialization for ints,
// in my actual code, I have many more specializations here
template <class Enabled>
struct Test <int, Enabled> 
{
    static void test (int dst)
    {
        cout << "Test<int>::test(" << dst << ")" << endl;
    }
};

// this should handle all subclasses of Base,
// but it doesn't compile
template <class T, class V, int i>
struct Test <T, typename enable_if <is_base_and_derived <Base <V,i>, T>>::type>
{
    static void test (const T &dst)
    {
        dst.doSomething();
    }
};

int main (int argc, char **argv)
{
    Test <int>::test (23);
    Test <Child>::test (Child());
    return 0;
}

The idea was that the specialization should handle all classes which are derived from Base with any arbitrary values of V and i. This does not work, gcc complains:

error: template parameters not used in partial specialization:
error:         ‘V’
error:         ‘i’

I guess the problem is that this approach would require the compiler to try all possible combinations of V and i to check if any of them matches. For now, I worked around the problem by adding something to the base class:

template <class V, int i>
struct Base
{
    typedef V VV;
    static constexpr int ii = i;
    static void doSomething() { cout << "something " << i << endl; };
};

This way, the specialization no longer needs to have V and i as free template parameters:

template <class T>
struct Test <T, typename enable_if <is_base_and_derived <Base <typename T::VV, T::ii>, T>>::type>
{
    static void test (const T &dst)
    {
        dst.doSomething();
    }
};

And then it compiles.

Now, my question is: How can I do this without modifying the base class? In this case it was possible because I wrote it myself, but what can I do if I have to handle third party library code in my Test template like that? Is there a more elegant solution?

Edit: Also, can someone give me a detailed explanation why exactly the first approach does not work? I have a rough idea, but I would prefer to have a proper understanding. :-)

like image 259
Benjamin Schug Avatar asked May 15 '12 13:05

Benjamin Schug


Video Answer


1 Answers

a simple solution is to let Base inherits another Base_base:

struct Base_base
{};

template <class V, int i>
struct Base
 :  public Base_base
{
    static void doSomething() { cout << "something " << i << endl; };
};

template <class T>
struct Test <T, typename enable_if <is_base_and_derived <Base_base, T>>::type>
{
    static void test (const T &dst)
    {
        dst.doSomething();
    }
};

[Edited] in a 3rd party code, you can use a trick like:

template <class V, int i>
struct Base3rdparty
{
    static void doSomething() { cout << "something " << i << endl; };
};

template <class V, int i>
struct Base
 :  public Base3rdparty<V, i>
{
    typedef V VV;
    static constexpr int ii = i;
};

template <class T>
struct Test <T, typename enable_if <is_base_and_derived <Base <typename T::VV, T::ii>, T>>::type>
{
    static void test (const T &dst)
    {
        dst.doSomething();
    }
};
like image 92
user2k5 Avatar answered Oct 25 '22 20:10

user2k5