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)
?
Pointers to members allow you to refer to nonstatic members of class objects. You cannot use a pointer to member to point to a static class member because the address of a static member is not associated with any particular object.
Defining a Pointer of Class type We can define pointer of class type, which can be used to point to class objects. Here you can see that we have declared a pointer of class type which points to class's object. We can access data members and member functions using pointer name with arrow -> symbol.
Member functions are operators and functions that are declared as members of a class. Member functions do not include operators and functions declared with the friend specifier. These are called friends of a class. You can declare a member function as static ; this is called a static member function.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With