I know there are similar questions on SO, but the usages there seem different to what I have. Here is my MRE:
#include <iostream>
#include <functional>
using namespace std;
void freeFunction() {}
struct Foo { void memberFunction() {} };
template<typename FunctionPtrT>
void foo(FunctionPtrT* f) { f(); }
template<typename InstanceT, typename FunctionPtrT>
void bar(InstanceT&& i, FunctionPtrT f) { std::mem_fn(f)(i); }
template<typename InstanceT, typename FunctionPtrT>
void baz(InstanceT&& i, FunctionPtrT* f) {}
int main() {
foo(&freeFunction); //Ok, obviously
bar(Foo(), &Foo::memberFunction); //Ok, how?!
// Error: candidate template ignored: could not match 'FunctionPtrT *' against 'void (Foo::*)()'
baz(Foo(), &Foo::memberFunction); //why?!
return 0;
}
Why is &Foo::memberFunction
not resolving to a pointer? What is the type of f
in bar
? If it's not a pointer how am I able to pass it to std::mem_fun
that is defined as template< class M, class T > /*unspecified*/ mem_fn(M T::* pm) noexcept;
?
CodePudding user response:
General suggestion for type investigations
I think the easiest way to answer questions like "what is the type of x
in this context" is to put a static_assert(std::is_same_v<void, delctype(x)>);
in that same context. The compiler will then tell you the static_assert
ion failed because void
is not equal to the type of x
, revealing what that type is.
is &Foo::memberFunction
a pointer?
And there are variations of that.
For instance, if you truly want to know wheter &Foo::memberFunction
is a pointer, then ask it this way:
static_assert(std::is_pointer_v<decltype(&Foo::memberFunction)>);
and the compiler will tell you something along the lines of
Static_assert failed due to requirement 'std::is_pointer_v<void (Foo::*)()>' [static_assert_requirement_failed]
so it just can't match against FunctionPtrT*
.
As regards
What is the type of
f
inbar
?
if you put
static_assert(std::is_same_v<void, decltype(f)>);
inside of bar
, the compiler will tell you
Static_assert failed due to requirement 'std::is_same_v<void, void (Foo::*)()>'
meaning that the type of f
is void(Foo::*)()
.
How does &Foo::memberFunction
's type from a free function pointer?
Compare this with the error you get if you define
void f();
and assert
static_assert(std::is_same_v<void, decltype(f)>);
The compiiler will tell you
Static_assert failed due to requirement 'std::is_same_v<void, void (*)()>'
So the point is that void(*)()
, a pointer to a free function from void
to void
, is different from void (Foo::*)()
, a pointer member of Foo
function from void
to void
.
How can I restrict a template to be instatiated only for member functions?
If you want your template to match only member functions, can go for
template<typename InstanceT, typename R, typename C>
void baz(InstanceT&& i, R C::* f) { }
or, if you want it to match any member function of Foo
, you can clearly either change the order of the template parameters such that you can provide C
manually
template<typename C, typename R, typename InstanceT>
void baz(InstanceT&& i, R C::* f) { }
which allows you to call baz<Foo>(/* args */);
, or you can simply hardcode Foo
in place of C
:
template<typename R, typename InstanceT>
void baz(InstanceT&& i, R Foo::* f) { }
And what if I want to restrict the match to member function with a specific signature?
As you see, we already have a placeholder R
, for the return type.
If we also wanted to have a placeholder for the arguments, we could go for this:
template<typename R, typename C, typename ...Args>
void baz(R (C::*f)(Args...));
Which will just match the same function as the previous example. I showed this just to make you see that, if you want to type the arguments out, you must put parenthesis around C::*f
, or just C::*
, if you want to omit the name (the lack of parenthesis is the typo in the comment under your question). Therefore, if you want to restrict the match to a specific signature, let's say void()
, this is how you do it:
template<typename C>
void baz(void (C::*f)());
CodePudding user response:
What is the type of f in bar?
The type of f
in bar
is void (Foo::*)()
i.e., a pointer to a member function of class Foo
that has no parameter and has the return type of void
.
The reason baz(Foo(), &Foo::memberFunction);
fails is that &Foo::memberFunction
is a pointer to a member function is distinct from an ordinary pointer to some type.
In fact, member function pointers are not actually pointers. So the argument of type void (Foo::*)()
cannot be passed to parameter FunctionPtrT* f
.