How to Pass a Std::Function Object to a Function Taking a Function Pointer

How do I pass a std::function object to a function taking a function pointer?

Is this possible?

No.

You can wrap a C-style callback into an std::function<>..., and the compiler will emit code to extract the data and the handler and call it. However, the exact code to do so will depend on the compiler, the ABI and the standard C++ library being used. You can't magically reconstruct that code given only the std::function wrapper.

Further, an arbitrary std::function... (or a lambda) may wrap code other than a call to C-style handler. It should be obvious that applying the "magically reconstructed" code to extract C-style handler from such an instance of std::function... can't possibly succeed.

P.S.

To put it differently, what I want is to be able to pass a stateful function to a c callback handler without having to manually instantiate my own struct type to pass along with it. Obviously std::function has already done this for us

So why don't you just use what std::function has already done for you:

void my_new_cpp_handler(std::function<void()>&& func)
{
func(); // calls some_c_handler(p, data) IFF that is what "func" encodes.
}

How to pass a std::function callback to a function requiring a typedef of a pointer to a function

struct {
console_cmd_func_t func;
} console_cmd_t;

void console_cmd_register(const console_cmd_t *cmd) {
// Register the command
}

Your C program is ill-formed. I'm going to assume that console_cmd_t isn't actually an instance of an unnamed struct as is depicted in the quoted code, but is rather a typedef name:

typedef struct {
console_cmd_func_t func;
} console_cmd_t;


How can I successfully register the command?

By using the types that the functions expect. They don't expect a std::function, so you may not use std::function. There's also no way to register a non-static member function, nor a capturing lambda.

A working example (assuming the correction noted above):

int my_callback(int, char **);
console_cmd_t my_struct {
.func = my_callback,
};
console_cmd_register(&my_struct);

In order to call a non-static member function, you would typically pass a pointer to the class as an argument into the callback. If the C API doesn't allow passing user defined arguments, then the only option is to use global state. Example:

static MyClass gobal_instance{};

int my_callback(int argc, char **argv)
{
gobal_instance.consoleCommandHandler(argc, argv);
}

To avoid global state, you need to re-design the C library.

std::function to C-style function pointer

You can use lambda functions with C-style function pointers, just not using std::function.

Lambdas that don't have any capture are convertible to a functions pointer:

using callback_t = int(*)(int arg, void* user_param);
void set_c_callback(callback_t, void* user_param);

// ...

void foo() {
set_c_callback([](int a, void* data) {
// code
}, nullptr);
}

But there is also a way with lambda with captures, using std::any:

// The storage can be inside a class instead of a global
std::any lambda_storage;

template<typename T>
void foo(T lambda) {
lambda_storage = lambda;
set_c_callback([](int n, void* user_data) {
auto& lambda = *std::any_cast<T>(static_cast<std::any*>(user_data));
lambda(n);
}, &lambda_storage)
}

// ...

foo([k = 1](int n) {
std::cout << n + k << std::endl;
});

std::function - function pointer

You've greatly oversimplified your real problem and turned your question into an XY problem. Let's get back to your real question: how to call SetupIterateCabinet with a non-static member function as a callback.

Given some class:

class MyClass
{
public:
UINT MyCallback(UINT Notification, UINT_PTR Param1, UINT_PTR Param2)
{
/* impl */
}
};

In order to use MyClass::MyCallback as the third argument to SetupIterateCabinet, you need to pass a MyClass* for the Context argument and use a plain shim function to take that Context argument and do the right thing with it:

UINT MyClassCallback(PVOID Context, UINT Notification, UINT_PTR Param1, UINT_PTR Param2)
{
return static_cast<MyClass*>(Context)->MyCallback(Notification, Param1, Param2);
}

int main()
{
MyClass mc;
SetupIterateCabinet(_T("some path"), 0, MyClassCallback, &mc);
}

how to pass member function pointer to std::function

I think the problem can be narrowed down to this:

template<class R, class... FArgs>
void test(std::function<R(FArgs...)> f)
{
}

int main() {
test(&SomeStruct::function);
}

The error message is pretty similar without the rest of the easy_bind stuff:

main.cpp: In function 'int main()':
main.cpp:63:31: error: no matching function for call to
'test(void (SomeStruct::*)(int, float, std::string))'
test(&SomeStruct::function);
main.cpp:63:31: note: candidate is:
main.cpp:49:10: note: template<class R, class ... FArgs>
void test(std::function<_Res(_ArgTypes ...)>)
void test(std::function<R(FArgs...)> f)
^
main.cpp:49:10: note: template argument deduction/substitution failed:
main.cpp:63:31: note: 'void (SomeStruct::*)(int, float, std::string)
{aka void (SomeStruct::*)(int, float, std::basic_string<char>)}'
is not derived from 'std::function<_Res(_ArgTypes ...)>'
test(&SomeStruct::function);

Essentially, it can't magically create an std::function for you. You need something like your Functor alias.


So thanks to the answer provided in generic member function pointer as a template parameter, here's what you can do:

//Test Case:
struct SomeStruct {
public:
int function(int x, float y, std::string str) {
std::cout << x << " " << y << " " << str << std::endl;
return 42;
}
};

template <typename Ret, typename Struct, typename ...Args>
std::function<Ret (Struct*,Args...)> proxycall(Ret (Struct::*mf)(Args...))
{
return std::function<Ret (Struct*,Args...)>(mf);
}

int main() {
auto func3 = fx::easy_bind(proxycall(&SomeStruct::function), new SomeStruct);
int ret = func3(5, 2.5, "Test3");
std::cout << ret << "\n";

return 0;
}

Now it works automatically.

Live Example

Pass member function pointer to parent class yields compiler error

If I understand the goal (and believe me, that's a sketchy 'if'), you want to specify some member of some A derivation to invoke from some A member as a dispatched 'callback' mechanic. If that is the case, then to answer your question in comment, yes, a function and bind can do this. It can even be semi-protected with a little help from sfinae:

Example

#include <iostream>
#include <type_traits>
#include <functional>
#include <memory>

struct A
{
virtual ~A() = default;

std::function<void(int)> callback = [](int){};

template<class Derived>
std::enable_if_t<std::is_base_of<A, Derived>::value>
registerCallback(void (Derived::*pfn)(int))
{
using namespace std::placeholders;
callback = std::bind(pfn, dynamic_cast<Derived*>(this), _1);
}

void fire(int arg)
{
callback(arg);
}
};

struct B : public A
{
void memberfn(int arg)
{
std::cout << __PRETTY_FUNCTION__ << ':' << arg << '\n';
}
};

struct Foo
{
void memberfn(int arg)
{
std::cout << __PRETTY_FUNCTION__ << ':' << arg << '\n';
}
};

int main()
{
std::unique_ptr<A> ptr = std::make_unique<B>();
ptr->registerCallback(&B::memberfn);
// ptr->registerCallback(&Foo::memberfn); // WILL NOT WORK
ptr->fire(42);
}

Output

void B::memberfn(int):42

The Parts

The first part is straight forward. We declare a member variable callback to be a std::function<void(int)> instance. This is where we'll eventually bind our callable object point. The default value is a lambda that does nothing.


The second part is... a little more complicated:

template<class Derived>
std::enable_if_t<std::is_base_of<A, Derived>::value>
registerCallback(void (Derived::*pfn)(int))

This declares registerCallback as an available member function that accepts a non-static member function pointer taking one int as an argument, but only if the class hosting that member function, or a derivative therein, is a derivation of A (or A itself). Some non-A derivative Foo with a member void foo(int) will not compile.


Next, the setup to the callback itself.

using namespace std::placeholders;
callback = std::bind(pfn, dymamic_cast<Derived*>(this), _1);

This just binds the pointer-to-member to this dynamic-cast to the derivation type (which had better work or we're in trouble, see final warning at the end of this diatribe), and sets the call-time placeholder. The _1 you see comes from the std::placeholders namespace, and is used to delay providing an argument to the callback until such time as we actually invoke it (where it will be required,and you'll see that later). See std::placehholders for more information.


Finally, the fire member, which does this:

void fire(int arg)
{
callback(arg);
}

This invokes the registered function object with the provided argument. Both the member function and this are already wired into the object. The argument arg is used to fill in the placeholder we mentioned earlier.


The test driver for this is straightforward:

int main()
{
std::unique_ptr<A> ptr = std::make_unique<B>();
ptr->registerCallback(&B::memberfn);
// ptr->registerCallback(&Foo::memberfn); // WILL NOT WORK
ptr->fire(42);
}

This creates a new B, hosting it in a dynamic A pointer (so you know there is no funny business going on). Even with that, because B derived from A the registerCallback sfinae filtering passes inspection and the callback is registered successfully. We then invoke the fire method, passing our int argument 42, which will be sent to the callback, etc.


Warning: With great power comes great responsibility

Even those there is protection from passing non-A derived member functions, there is absolutely none from the casting itself. It would be trivial to craft a basic A, pass a B member (which will work since A is its base), but there is no B actually present.

You can catch this at runtime via that dynamic_cast, which we're currently not error checking. For example:

registerCallback(void (Derived::*pfn)(int))
{
using namespace std::placeholders;
Derived *p = dynamic_cast<Derived*>(this);
if (p)
callback = std::bind(pfn, p, _1);
}

You can choose the road more risky. Personally, i'd detect the null case and throw an exception just to be safe(er)



Related Topics



Leave a reply



Submit