Определение указателя функции С++

Рассмотрим этот код:

#include <iostream>
#include <functional>

struct B {
    template <class C, class M, class T>
    void call1(C (M::*member)(), T *instance) {
        std::function<void()> fp = std::bind(member, instance);
        fp();
    }

    template <class C, class M, class T>
    void call2(C (M::*member), T *instance) {
        std::function<void()> fp = std::bind(member, instance);
        fp();
    }

    void foo() {
        call1(&B::func, this); // works
        call2(&B::func, this); // works

        call1(&B::func2, this); // Error: no matching member function for call to 'call2'
        call2(&B::func2, this); // works
    }

    void func() {
        std::cout << "func\n";
    }

    void func2() const volatile {
        std::cout << "func2\n";
    }
};

int main() {
    B{}.foo();
}

Похоже, что более поздняя версия не принимает функции с дополнительными cv-классификаторами.

В чем разница между указателем элемента функции, подобным этому C (M::*member)(), и как этот C (M::*member)?

Ответы

Ответ 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. Вот почему это работает.