Tuesday, January 13, 2009

c++: partial template specialization of class methods

In c++ it's possible to specify class method of template class.
Before I thought it's only possible to specify the whole class and then redefine functions. It could be painful if template class contains a lot of methods. Of course the expected class could be built deriving from template class specifying the new type and redefining needed functions:

template<typename T>
class B
{
    public:
        void operator ()()
        {  
            std::cout << "B<" << typeid(T).name() << ">::operator ()" << std::endl;
        }  
        void method()
        {  
            std::cout << "B<" << typeid(T).name() << ">::method()" << std::endl;
        }  
};

class C: public B<int>
{
    public:
        void operator ()()
        {  
            std::cout << "C<long#pseudo class specialization>::operator () with deriving from B<int>" << std::endl;
        }  
};
int main(int argc, char **argv)
{
    C()();
    C().method();
}
The output you expect should be
C<long#pseudo class specialization>::operator () with deriving from B<int>
B<i>::method()
We get the class C which is the same as B<int> but with custom operator ().
Unfortunately this code changes the name of the class and developer should keep in mind that class C is B<int> with custom functions. This is not explicit even if you find better name than C.

Another way, that is a c++ way, to specify methods of template class. Let's say we have class A defined below:
template<typename T0>
class A
{
    public:
        void operator ()()
        {
            std::cout << "A<" << typeid(T0).name() << ">::operator ()" << std::endl;
        }
        template<typename T1>
        void method()
        {
            std::cout << "A<" << typeid(T0).name() << ">::method<" << typeid(T1).name() << ">()" << std::endl;
        }
};
Here we can redefine both operator () and method() class methods:
template<>
void
A<float>::operator ()()
{
    std::cout << "A<float#method specialization>::operator ()" << std::endl;
}

template<>
template<>
void
A<float>::method<float>()
{
    std::cout << "A<float#method specialization>::method<float#method specialization>()" << std::endl;
}
operator () for A<float> and method<float> for A<float> have been specified. What actually happen? There was created fully specified class A for type float and both of its methods have been defined.
The output of
int main(int argc, char **argv)
{
    A<int>()();
    A<float>()();
    A<float>().method<float>();

    return 0;
}
should be
A<i>::operator ()
A<float#method specialization>::operator ()
A<float#method specialization>::method<float#method specialization>()
You can see that appropriate methods have been called.

As I mentioned before, if you do a class specialization you have to redefine all methods:
template<>
class A<double>
{
    public:
        void operator ()()
        {  
            std::cout << "A<double#class specialization>::operator ()" << std::endl;
        }  
        template<typename T1>
        void method()
        {  
            std::cout << "A<double#class specialization>::method<" << typeid(T1).name() << ">()" << std::endl;
        }  
};
Otherwise if you don't define method for A<double> and it's used somewhere in this context compiler will rise an error that no member method was defined in class A<double>. Class specialization should be used instead of class method specialization if the specialization changes the behavior of the most members of the class. Doing specialization you are defining new class with the same as template class but optimized for special case. Using the code above the next program
int main(int argc, char **argv)
{
    A<double>()();
    A<double>().method<int>();

    return 0;
}
should produce
A<double#class specialization>::operator ()
A<double#class specialization>::method<i>()
You see that methods from A<double> have been called.

And even in the case of template class specialization you still able to specify its template methods.
template<>
void
A<double>::method<double>()
{
    std::cout << "A<double#class specialization>::method<double#method specialization>()" << std::endl;
}
The program
int main(int argc, char **argv)
{
    A<double>().method<int>();
    A<double>().method<double>();

    return 0;
}
should show on stdout next messages
A<double#class specialization>::method<i>()
A<double#class specialization>::method<double#method specialization>()
At first 'undefined' method of A<double> was called and later specialized one.

Template specialization is a powerful mechanism and should be used with comprehension.

4 comments:

bernievoigt said...

Hi Dmytro,

thanks for the nice tutorial. It helped me a lot. When you define the speciliazed methods outside of the class definition it might be important to inline the specialized methods. Otherwise, I did get multiple definition errors during linking.

Ni@m said...

Hello, bernievoigt.
That's great that this post was helpful for you! Thank you!

For sure you have to include any template class or function definition into the compile module because template entities are being constructed on compilation stage. There is "export" C++ keyword which forces compiler to put definition of template entity into the object file. But it's not supported by all compilers. Actually I haven't ever tried it.

Кот Сибирский said...

Why the title does say "partial template specialization of class methods"?

template<>
template<>
void
A::method()
{}
is a full specialization.

Ni@m said...

Yes, for sure that's a full specification of a method, but I meant that you partially define the class behaviour.