-1

I have a problem with casting: the std::function <int32_t (std::string &, uint32_t)> to the typedef int32_t (*callback_c_type) (std::string &, uint32_t (C function pointer).

My full example: https://onlinegdb.com/5KI36oPlQ

#include <iostream>
#include <functional>
using namespace std;

typedef int32_t (*callback_c_type) (std::string &, uint32_t);
static int counter = 0;

int32_t my_callback (std::string & index, uint32_t var_id)
{
  std::cout << "my_callback => index: '" << index 
  << " var_id: '" << var_id 
  << "'" << " counter = " << counter << std::endl;
  counter++;
  return 42;
}

void execute_c_callback(callback_c_type cb)
{
  std::cout << "execute_c_callback" << std::endl;
  std::string text = "foo";
  cb(text, 777);
}

int main ()
{
  callback_c_type cb = &my_callback;
  execute_c_callback(cb);

  std::function <int32_t (std::string &, uint32_t)> cb_2 = cb;
  // execute_c_callback((callback_c_type)cb_2); 
  // PROBLEM: convert std::function<int32_t(std::string&, uint32_t)> -> callback_c_type
    
  return 0;
}
Michał Hanusek
  • 199
  • 3
  • 13
  • 6
    What is the point of casting to `const char*` and back? It doesn't add anything but problems. – molbdnilo Nov 09 '21 at 13:59
  • `register_c_callback` should probably accept `callback_c_type` argument instead of `char const *`. This won't help with passing an `std::function` object though. – user7860670 Nov 09 '21 at 14:02
  • 2
    Please be more specific than "I have a problem". And don't post links to code, post the code. – molbdnilo Nov 09 '21 at 14:03
  • 1
    I think a [mcve] would be helpful. But from what I can see, I don't believe this can be done with casts. You likely need an adapter function/object of some sort. – Fred Larson Nov 09 '21 at 14:03
  • @molbdnilo Because the external C library requires it. I have to use `const char*` to pass the function pointer. @Fred Larson https://onlinegdb.com/Sk9GIVTZa – Michał Hanusek Nov 09 '21 at 14:05
  • 1
    What library is that? Who made that library? Are you _sure_ that the library casts `const char *` pointer into a function pointer to call that pointer? If the library does that, do not use that library - for sure it's a very bad library. || The presented code is not working, becuase `)>*>(&cb);` - `&cb` is the address of `cb` pointer, not it's value. Maybe you want to `reinterpret_cast<....>(cb)`. Still, it depends on some assumptions - consider adding at least `static_assert(alignof(const char *) == alignof(std::function<....>)`. – KamilCuk Nov 09 '21 at 14:07
  • If it's a C library, you can't use any C++ types and must stick to the appropriate C prototype. `int32_t (std::string &, uint32_t)` can't possibly work. I suspect that you have misunderstood the callback interface. – molbdnilo Nov 09 '21 at 14:09
  • From the link it appears you are trying to do `func(text, 123)` where `func` is actually of type `std::function *`. Please edit your question to provide all relevant code/information as text. – G.M. Nov 09 '21 at 14:09
  • @KamilCuk You certainly can’t/don’t want to cast `cb` to `char const*`. Instead you’d cast the pointer (as the code currently does), then inside the function cast it back and dereference it. The code is trivial to fix (just make `func` inside the function a reference) but of course that won’t help OP with the actual library. – Konrad Rudolph Nov 09 '21 at 14:20
  • I do binding to a library written in rust. I want to pass a c++ callback(std::function) to rust library by `const char*`. Classic C function pointer working correctly. https://doc.rust-lang.org/nomicon/ffi.html#callbacks-from-c-code-to-rust-functions https://cxx.rs/ – Michał Hanusek Nov 09 '21 at 14:23
  • 2
    @MichałHanusek But (a) why would the type of the Rust callback be `char const*`? And (b) you still can’t pass a `std::function` to the Rust library. It expects a C function pointer, not a pointer to a `std::function` object. – Konrad Rudolph Nov 09 '21 at 14:24
  • There is an underlying question that can be answered, but it's hidden behind the issue of using `char const*` for a callback. The Rustonomicon uses `typedef void (*rust_callback)(int32_t);` to register a callback, which is a proper function pointer type. If you change your example to use proper function pointer type, then maybe people are willing to answer the underlying question about `std::function` vs pointers. – dyp Nov 09 '21 at 14:40

1 Answers1

0

std::function is a class (template) with a misleading name: It is not a function. It is rather a polymorphic function (object) wrapper. Let's take that apart: it's a wrapper class for functions and function objects, and it's polymorphic in the sense of type erasure.

The main job of std::function is to store arbitrary callable objects (function objects) and provide a single interface for them:

double myFunc(int);

struct MyStruct {
    double operator()(int);
};

std::function<int(double)> f = myFunc; // can store function pointers
f = MyStruct{}; // or class instances
// ... and many more

The type of f is the same, while the type of the thing stored inside can differ. This is called type erasure.


A C callback with signature int32_t (*) (std::string &, uint32_t) expects a function pointer. In other words, a pointer to a piece of executable code.

You cannot pass a class object as a function pointer, since a class object is not a piece of code but a piece of data, with related pieces of code (the member functions).

Thinking about it in terms of state, a function is stateless. It's always the same piece of code. A class object can be stateful (if it has any members, for example).

A function pointer is a single pointer to executable code, it cannot transfer state directly. Therefore, many C callbacks have an additional parameter to pass state:

typedef int32_t (*callback_c_type) (void* state, std::string &, uint32_t);

With such a signature, you can pass the function object as a pointer through the callback "bottleneck" and restore it on the other end, but with caveats:

typedef int32_t (*callback_c_type) (void*, std::string &, uint32_t);

void execute_c_callback(callback_c_type cb, void* state)
{
  std::cout << "execute_c_callback" << std::endl;
  std::string text = "foo";
  cb(state, text, 777);
}

using plain_cb_signature = int32(std::string&, uint32_t);

int32_t callback_wrapper(void* state, std::string &s, uint32_t i)
{
    auto* f = static_cast<std::function<plain_cb_signature>*>(state);
    (*f)(s, i);
}

int main ()
{
  std::function <int32_t (std::string &, uint32_t)> cb_2 = cb;
  auto* f = &cb;
  execute_c_callback(callback_wrapper, f); 
    
  return 0;
}

The trouble here is maintaining the lifetime of cb, in other words, keeping the state valid long enough for all invocations of execute_c_callback.

Usually one writes callback_wrapper as a generic function template. See, for example, this version by Yakk.

dyp
  • 38,334
  • 13
  • 112
  • 177