Paul Houx
Paul Houx

Reputation: 2043

How to specialize a function template with more than 1 parameter?

Consider the following templated member function:

template<typename E, typename N, typename P>
void Node::connectEvent( const bool( N::*fn )( const P& ), N *inst )
{
    // Obtain unique event ID based on type.
    size_t eventId = typeid( E ).hash_code();

    // Actual code wraps the function returned from std::bind, 
    // but for this example let's assume we can store it directly. 
    mCallbacks[eventId] = std::bind( fn, inst, std::placeholders::_1 );
}

I'd like to be able to call this function in the following way:

connectEvent<MouseDownEvent>( &MyNode::mouseDown, this );

, where the callback function is defined as:

bool MyNode::mouseDown( const MouseDownEvent &event );

, or even using a base class as parameter, which is why I have a separate event type E and parameter type P in the template:

bool MyNode::mouseDown( const Event &event );

I also need support for this:

connectEvent<DrawEvent>( &MyNode::draw, this );

, where the callback function is defined as:

bool MyNode::draw();

Question: to support the latter, I want to specialize the connectEvent function for the case where parameter P is void, because it requires a different call to std::bind. I've tried many different approaches, including using a combination of enable_if and is_void on the base template, but none compiled, so I must be doing something wrong and have resorted to trial-and-error at this point.

In most cases, the Visual Studio 2015 compiler complains about "illegal use of explicit template arguments".

Here's a version of the code that I thought would work, but didn't:

template<typename E, typename N>
void Node::connectEvent<E,N,void>( const bool( N::*fn )(void), N *inst )
{
    size_t eventId = typeid( E ).hash_code();
    mCallbacks[eventId] = std::bind( fn, inst );
}

What should I change in my code to make this possible?

Upvotes: 0

Views: 82

Answers (2)

Paul Houx
Paul Houx

Reputation: 2043

Thanks to the answers given here, especially T.C.'s remarks, I realized the problem lies in the combination of the two functions: even if we could write two valid versions, the compiler would not be able to properly distinguish between them because it treats the void as a parameter and always tries to construct the first version. It then reports an error trying to use the const void &param.

The only solution that worked, was using a different name for the connectEvent function, e.g.:

template<typename E, typename N>
void Node::connectVoidEvent( const bool( N::*fn )( ), N *inst );

, so that the compiler does not get confused about which version I want to use. I'll probably not going to use this, though, and will instead always require a single parameter for the callback method.

For what it's worth: I also tried to do this instead, shifting the problem to the call to std::bind, but couldn't figure out where to go from there:

template<typename E, typename F, typename N>
void Node::connectEvent( const F &fn, N *inst )
{
    // F has type:          (__thiscall MyNode::*)(void)
    // or something like:   (__thiscall MyNode::*)(const Event&)

    size_t eventId = typeid( E ).hash_code();

    // How will the compiler know the number of parameters of F,
    // and whether or not to use placeholders?
    mCallbacks[eventId] = std::bind( fn, inst, std::placeholders::_1 );
}

But that's a different question. Anyway, thanks for everyone's input.

Upvotes: 0

Jens
Jens

Reputation: 9416

Yout cannot partially specialize function templates. In your case, you could just define a second overload template with only two template parameters.

template<typename E, typename N, typename P>
void Node::connectEvent( const bool( N::*fn )( const P& ), N *inst ) { ...}

template<typename E, typename N>
void Node::connectEvent( const bool( N::*fn )(), N *inst ) { ...}

Specialising function template is usually not a good idea anyway. The work-around would be to define a template function and dispatch internally to a template class which can be partially specialized:

template<typename E, typename N, typename P> struct C
{
    static void connectEvent(const bool( N::*fn )( const P& ), N *inst)
    {
        size_t eventId = typeid( E ).hash_code();
        mCallbacks[eventId] = std::bind( fn, inst );
    }

};

 template<typename E, typename N> struct C<E,N,void> {
    ... specialize ...
 };

template<typename E, typename N, typename P>
void Node::connectEvent( const bool( N::*fn )( const P& ), N *inst ) 
{
    C<E,N,P>::connectEvent(fn, inst);
}

template<typename E, typename N>
void Node::connectEvent( const bool( N::*fn )(), N *inst ) 
{
    C<E,N,void>::connectEvent(fn, inst);
}

Upvotes: 2

Related Questions