Crossfire
Crossfire

Reputation: 1861

C++ Member Function Pointer Definition

Consider this code:

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

It seems that the later version don't accept functions with extra cv qualifiers.

What is the difference between specifying a function member pointer like this C (M::*member)() and like this C (M::*member)?

Upvotes: 16

Views: 1094

Answers (1)

Barry
Barry

Reputation: 303347

Let's simplify to just considering the difference between:

template <class C, class M> void f(C (M::*member)());
template <class C, class M> void g(C (M::*member));

In f, member is a pointer to a member function of M returning taking zero arguments and returning C. If you called it with &B::func, the compiler will deduce M == B and C == void. Straightforward.

In g, member is just a pointer to a member of M of type C. But, in our case, &B::func is a function. So the impact here is just dropping the pointer. We deduce M == B again, whereas C becomes void() - now C is a function type. This is a less specialized version of f in that it allows for more kinds of members. g can match against functions that take arguments, or against pointers to members, or, relevantly, against cv-qualified member functinos.

Let's consider as an example an overloaded function and how it would get deduced differently (this was the original problem in your question, which has since been edited, but is still interesting):

struct X {
    void bar() { }
    void bar(int ) { }
};

When we do:

f(&X::bar);

Even though &X::bar is an overloaded name, only one actually matches C (M::*)(). The one that has M == X and C == void. There is simply no way that the overload of bar taking an int matches the template type. So this is one of the acceptable uses of passing an overloaded name. This deduces fine.

However, when we do:

g(&X::bar);

Now, there are two perfectly vaild deductions. C could be both void() and void(int). Since both are valid, the deduction is ambiguous, and you fail to compile - with an error that doesn't really make this particularly clear.


Now back to your example:

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

The type of &B::func2 is void (B::*)() const volatile. Since call1 deduces on a member function type that takes no args and isn't cv-qualified, the type deduction simply fails. There is no C or M to get those types to match.

However, the call2 deduction is fine since it is more generic. It can match any pointer to member. We simply end up with C = void() const volatile. That's why this works.

Upvotes: 15

Related Questions