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
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With