Как специализировать шаблон функции с более чем 1 параметром?

Рассмотрим следующую шаблонную функцию-член:

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 );
}

Я хотел бы иметь возможность вызывать эту функцию следующим образом:

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

, где функция обратного вызова определяется как:

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

, или даже с использованием базового класса в качестве параметра, поэтому у меня есть отдельный тип события E и тип параметра P в шаблоне:

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

Мне также нужна поддержка для этого:

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

, где функция обратного вызова определяется как:

bool MyNode::draw();

Вопрос: для поддержки последнего я хочу специализировать функцию connectEvent для случая, когда параметр P недействителен, поскольку для этого требуется другой вызов std::bind. Я пробовал много разных подходов, в том числе использовал комбинацию enable_if и is_void в базовом шаблоне, но ни один из них не скомпилировался, поэтому я, должно быть, делаю что-то не так и прибегаю к методу проб и ошибок на этом этапе.

В большинстве случаев компилятор Visual Studio 2015 жалуется на «незаконное использование явных аргументов шаблона».

Вот версия кода, которая, как я думал, будет работать, но не сработала:

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 );
}

Что я должен изменить в своем коде, чтобы это стало возможным?


person Paul Houx    schedule 21.12.2015    source источник
comment
Почему для этого нужна специализация? Разве это не должна быть просто новая функция шаблона только с двумя параметрами? (т.е. вообще игнорировать пустоту)   -  person akhisp    schedule 21.12.2015
comment
Попытка сформировать параметр типа const void & является ошибкой. Это не дает вам пустой список параметров.   -  person T.C.    schedule 21.12.2015
comment
Трудно понять, как этот дизайн должен работать. Как вы решаете, будете ли вы передавать параметр при вызове обратного вызова?   -  person M.M    schedule 22.12.2015


Ответы (2)


Вы не можете частично специализировать шаблоны функций. В вашем случае вы можете просто определить второй шаблон перегрузки только с двумя параметрами шаблона.

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 ) { ...}

Специализация шаблона функции в любом случае не является хорошей идеей. Обходным путем может быть определение функцию шаблона и внутреннюю отправку в класс шаблона, который может быть частично специализирован:

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);
}
person Jens    schedule 21.12.2015
comment
P все равно никогда не может быть void. - person T.C.; 21.12.2015
comment
@Т.С. Не в этом примере, но я хотел показать общий обходной путь для специализации шаблонов функций. - person Jens; 21.12.2015

Благодаря приведенным здесь ответам, особенно замечаниям TC, я понял, что проблема заключается в сочетании двух функций: даже если бы мы могли написать две действительные версии, компилятор не смог бы должным образом различить их, потому что он обрабатывает void в качестве параметра и всегда пытается построить первую версию. Затем он сообщает об ошибке при попытке использовать файл const void &param.

Единственным решением, которое сработало, было использование другого имени для функции connectEvent, например:

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

, чтобы компилятор не запутался, какую версию я хочу использовать. Однако я, вероятно, не буду использовать это, и вместо этого всегда буду требовать один параметр для метода обратного вызова.

Для чего это стоит: Я также попытался сделать это вместо этого, переложив проблему на вызов std::bind, но не мог понять, куда идти оттуда:

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 );
}

Но это другой вопрос. В любом случае, спасибо всем за участие.

person Paul Houx    schedule 21.12.2015