Ответ 1
Пусть упрощается просто рассмотрение разницы между:
template <class C, class M> void f(C (M::*member)());
template <class C, class M> void g(C (M::*member));
В f
, member
- это указатель на функцию-член из M
, возвращающий нулевые аргументы и возвращающий C
. Если вы вызвали его с помощью &B::func
, компилятор выведет M == B
и C == void
. Непосредственная.
В g
, member
- это просто указатель на элемент M
типа C
. Но в нашем случае &B::func
является функцией. Таким образом, влияние здесь - это просто удаление указателя. Мы снова выводим M == B
, тогда как C
становится void()
- теперь C
является типом функции. Это менее специализированная версия f
в том, что она позволяет больше типов членов. g
может сопоставляться с функциями, которые принимают аргументы, или против указателей на членов, или, соответственно, против cv-квалифицированных членов functinos.
Рассмотрим в качестве примера перегруженную функцию и как она будет выводиться по-разному (это была исходная проблема в вашем вопросе, которая с тех пор была отредактирована, но все еще интересна):
struct X {
void bar() { }
void bar(int ) { }
};
Когда мы делаем:
f(&X::bar);
Даже если &X::bar
является перегруженным именем, только один фактически соответствует C (M::*)()
. Тот, который имеет M == X
и C == void
. Просто нет способа, чтобы перегрузка bar
с помощью int
соответствовала типу шаблона. Таким образом, это одно из приемлемых способов использования перегруженного имени. Это выводит штраф.
Однако, когда мы делаем:
g(&X::bar);
Теперь есть два отличных вывода. C
может быть как void()
, так и void(int)
. Поскольку оба значения действительны, вывод является неоднозначным, и вы не можете скомпилировать - с ошибкой, которая на самом деле не делает это особенно ясным.
Теперь вернемся к вашему примеру:
call1(&B::func2, this); // Error: no matching member function for call to 'call2'
call2(&B::func2, this); // works
Тип &B::func2
равен void (B::*)() const volatile
. Поскольку call1
выводит на тип функции-члена, который не принимает аргументов и не имеет квалификацию cv, то вывод типа просто терпит неудачу. Для получения этих типов нет C
или M
.
Однако вывод call2
прекрасен, поскольку он более общий. Он может соответствовать любому указателю на элемент. Мы просто получаем C = void() const volatile
. Вот почему это работает.