I want to be able to defer calls to member functions of some object. Therefore, I need to be able to store member function pointers plus parameters as data.
My approach uses C++ templates that treat the member function pointers as part of the type. The function pointer should be a template parameter, as I want to be able to select via static_assert at compile time, which member functions are allowed (ie. only setters);
I am using gcc (GCC) 14.1.1 20240507 and C++ 23.
Example Code:
struct Object {
void set();
void set(int test);
int get();
};
// Utils:
template <typename Function>
struct FunctionTraits;
template <
typename Return,
typename... Args
> struct FunctionTraits
<Return (Object::*)(Args...)>
{
using args_tuple_t = std::tuple<Args...>;
using ret_t = Return;
};
// The member function wrapper:
template <
typename Function,
Function function
>
struct SetterTask
{
Object* obj;
typename FunctionTraits<decltype(function)>::args_tuple_t args;
};
// pseudo constructor
template <
typename Return,
typename... Args
>
auto makeSetter(
Object* obj,
Return (Object::*setter)(Args...),
Args... args
)
{
auto tuple = std::tuple( args... );
using SetterType = Return (Object::*)(Args...); // <-- ERROR
return SetterTask<SetterType,setter>{ // <-- ERROR
.obj = obj,
.args = tuple
};
}
template <
typename Function,
Function function
>
auto run(
SetterTask<Function, function> setter
)
{
return std::apply(setter.function,
std::tuple_cat(
std::make_tuple(setter.obj),
setter.args
)
);
}
Test program:
void test() {
void (Object::*pFunc)(int) = &Object::set;
Object obj;
auto setter = makeSetter( &obj, pFunc, 10 );
run(setter);
}
This is the error I get:
template_test.cpp:10:26: required from here
10 | auto setter = makeSetter( &obj, pFunc, 10 );
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
template_test.h:61:9: error: ‘setter’ is not a valid template argument for type ‘void (Object::*)(int)’
61 | };
| ^
template_test.h:61:9: note: it must be a pointer-to-member of the form ‘&X::Y’
What am I doing wrong? To my understanding, the types are all clear, even overly explicit. Or is this a compiler bug?
This is the critical section:
template <
typename Return,
typename... Args
>
auto makeSetter(
Object* obj,
Return (Object::*setter)(Args...),
Args... args
)
{
auto tuple = std::tuple( args... );
using SetterType = Return (Object::*)(Args...); // <-- ERROR
return SetterTask<SetterType,setter>{ // <-- ERROR
.obj = obj,
.args = tuple
};
}
I also tried:
using SetterType = decltype(setter); <-- decltype of pointer
return SetterTask<SetterType,setter>{
...
};
...and...
using SetterType = Return (Object::*)(Args...);
return SetterTask<SetterType,(SetterType)setter>{ <-- explicitly cast pointer
...
};
std::function?std::functionis pretty much the big thing that can store "anything" that remotely looks or smells like it's something that can be called. This includes objects created by thestd::bindfamily, or lambda functions, as ways to attach data to the callables.setter:Return (Object::*setter)(Args...),cannot be used as template instantation argument. It does not only apply to pointers to members - it applies to all non-type, non-template template parameters (if nothing changes in C++23 in this area)...