views:

507

answers:

1

I have a templated class and inside I have a templated function( different template parameters ) and I having issues getting the compiler to call the correct one.

Example:

template< class Parm1, class Parm2, class Parm3 >
class Class {
public:
   void   Func( Parm1 arg1, Parm2 arg2 ) {
      Call<Parm3>( arg1, arg2 ); 
   }

protected:
   template< class Type >
   void   Call( Parm1 arg1, Parm2 arg2 ) {
   }

   template<>
   void   Call<void>( Parm1 arg1, Parm2 arg2 ) {
   }
};

So if the type of Parm3 is 'void' I want the second Call to be called. Otherwise the first. VS it works fine but GCC pukes all over it. It always calls the first one. Now is this an issue with specializing within of an unspecialized class or does it have something to do with the fact that Im specializing with 'void'

Any help would be great. Thanks.

+3  A: 

Yes, explicitly specializing a function without fully specializing all outer template is not possible (an explicit function specialization is a real function - there can't be any "variable parts" around it that are still parameterized by a template)

A simple way is to use a type2type template together with overloading:

template<typename T> struct t2t { typedef T type; };

void Func( Parm1 arg1, Parm2, arg2 ) { Call<Parm3>(arg1, arg2, t2t<Parm3>()); }
template< class Type, class V >  void Call( Parm1 arg1, Parm2 arg2, t2t<V>) { }
template< class Type >  void Call( Parm1 arg1, Parm2 arg2, t2t<void>) { }

Now, it will call the second Call overload if you call it with t2t<void>, and the first otherwise, because the first one is less special.

Using enable_if is possible too:

void Func( Parm1 arg1, Parm2, arg2 ) { Call<Parm3>(arg1, arg2); }

template< class Type > typename disable_if< is_same<Type, void> >::type
Call( Parm1 arg1, Parm2 arg2) { }

template< class Type > typename enable_if< is_same<Type, void> >::type 
Call( Parm1 arg1, Parm2 arg2) { }

Now, the second one is taken if Type is void, and the first one is taken if Type is something else again. But using a different technique. This one is called SFINAE. An alternative way, but which again adds one parameter is this - to demonstrate the way SFINAE works:

void Func( Parm1 arg1, Parm2, arg2 ) { Call<Parm3>(arg1, arg2); }

template< class Type >
void Call( Parm1 arg1, Parm2 arg2, char(*)[!is_same<Type, void>::value] = 0) { }

template< class Type > 
void Call( Parm1 arg1, Parm2 arg2, char(*)[ is_same<Type, void>::value] = 0) { }

SFINAE happens if the substitution of a template parameter yields to an invalid type or construct. Below, we try to create a pointer to an array of size 0 or 1 respectively. An array of size 0 is not valid, and will cause an SFINAE failure - the corresponding template specialization will not be considered as a call-candidate if it is a function.

In the enable_if case above, it works different. If enable_if is given something derived from false_type, then it makes its ::type typedef not existent. is_same derives itself from false_type in the case types are not the same. We would then try to access a not existent name - which is an invalid construct and would therefor be an SFINAE failure too.

Johannes Schaub - litb
Very interesting info litb, but I'm having trouble with the code -- are Func() and Call() supposed to be defined inside Class?
j_random_hacker
yes. i removed the note now :) i meant that comment only for t2t, because it's useful for other cases too and could be defined in some utility namespace :)
Johannes Schaub - litb
I see, very clever! (Took me some time to figure out exactly what was going on there...) Am I correct in thinking that you use t2t just in case V is either large or lacks a default ctor? BTW it would clarify things if you could put the last 3 lines of snippet #1 inside the definition of Class.
j_random_hacker
Another thing: Based on this, I'm thinking that perhaps template overloading is strictly more powerful than explicit specialisation -- am I right? I.e. is there anything that could be accomplished using the latter that could not also be accomplished using the former?
j_random_hacker
j_random_hacker, i wouldn't want to pass a V object to the function. that is why we just pass a empty class to it, which has the type of V encoded in its own type. it would work if we would pass a V* too. but this way it's cleaner and it also works for references.
Johannes Schaub - litb
overloading function templates is much more powerful, indeed. i think (but i don't know for sure. haven't ever done a formal analysis for it) every case of explicit specialization of a function template can be solved by overloading it. lookout for "partial ordering of function templates"
Johannes Schaub - litb
j_random_hacker, those int-to-type and type-to-type transformer are discussed in alexandrescu's book "modern c++ design". i highly recommend it if you want to look at some template design techniques.
Johannes Schaub - litb
@litb: I see, thanks. My guess is that explicit specialisation was just added to C++ at an earlier stage because it was easier to understand and implement, and then they had to leave it in for compatibility after they later added proper overloading based on partial ordering.
j_random_hacker