Partial specialization of double-templated method fails

3k views Asked by At

There is the template class List.

template <typename Point>
class List
{


    public:
          template <const unsigned short N>
          void load ( const char *file);
          ...
};

template <typename Point>
template <const unsigned short N>
void List <Point>::load ( const char *file)
}

How to specialize method load for N=2? This code is not valid...

template <typename Point>
void List <Point> <2>::load ( const char *file)
{
}

And this code also does not work.

template <typename Point>
void List <Point> ::load <2> ( const char *file )
{ 
}

Error 3 error C2768: 'List<Point>::load' : illegal use of explicit template arguments 66. 
Error 5 error C2244: 'List<Point>::load' : unable to match function definition to an existing declaration 66

Compiler g++:

template <typename Point>
template <>
void List <Point> ::load <2> ( const char *file )
{
}

error: explicit specialization in non-namespace scope `class List<>'
error: enclosing class templates are not explicitly specialized
error: default arguments are only permitted for function parameters
error: `load' is not a function template
error: invalid function declaration
3

There are 3 answers

3
templatetypedef On BEST ANSWER

It turns out that there's a provision in the C++ spec that explicitly disallows specializing a template class or function nested inside of a template class unless you also explicitly specialize the outer template as well. Visual Studio doesn't enforce this rule, hence the confusion with the previous example, but g++ certainly does.

If you want to specialize the template, your options will either be to also specialize the outer template or to somehow fake up the behavior of specialization by having the method dispatch to one of two different implementations based on the template parameter. Neither of these are very satisfying, I know, but unfortunately the language is designed weirdly in some template corners. :-(

One way that you can emulate the behavior of the explicit specialization is to use a technique called tag dispatching. The idea is that we'll make a very simple struct that looks like this:

template <unsigned short N> struct Box {};

This type is completely empty. It's not meant to be used directly, but rather is just a way of embedding an integer into the type system. In particular, Box<3> is not the same type as Box<4>, etc.

Next, in your list class, define two functions that look like this, preferably marked private:

template <unsigned short N>
    void doLoad(const char* file, Box<N>);
void doLoad(const char* file, Box<2>);

These two functions are overloads of one another, distinguishable only by their final parameter, which is either a Box<N> in the template case or a Box<2> in the non-template case. Note that the parameters don't have names. This is an arbitrary decision, but since we're not planning on actually reading the parameters, we don't need them. The intuition behind these functions is that this first function will be the "catch-all" implementation that will work for any N except 2. The second version will contain the implementation of loading for the case where N == 2.

Finally, implement load as follows:

template <typename Point>
    template <unsigned short N>
void List<Point>::load(const char* file) {
    doLoad(file, Box<N>());
}

How does this work? This function takes in a parameter, and then calls doLoad forwarding that parameter as the first argument and passing a temporary Box<N> as the second argument. If N is not two, then this is a call to the template version of doLoad, which is the catch-all handler. If, on the other hand, N is two, then this will call the non-template version of doLoad, because non-template functions have priority over template functions during overload resolution.

In short, the implementation of load just becomes a trampoline to forward you to the correct of the two implementations. You can then put the logic in the appropriate doLoad function to get the behavior you want.

Hope this helps!

2
Bo Persson On

You cannot specialize a member template without also specializing the class template.

I also wonder what the meaning of N could be, as it is not used in the function parameter?

7
Xeo On

Edit: Ok, so I rewrote your class a bit, with inlined function definitions, and this definitely works:

template <typename Point>
class List
{
public:
    template <const unsigned short N>
    void load( const char *file){
    }

    template<>
    void load<2>(const char* file){
    }
};