1

I'd like to build a C++ library which is usable from C as well. This is the header file I want to be able to compile in C:

typedef void (*log_function_t)(const char *);
typedef void (*delay_function_callback_t)(uint32_t);
typedef void (*delay_function_t)(uint32_t, delay_function_callback_t);

extern "C" void core_init(log_function_t logFunction, delay_function_t delayFunction);

However, since I'm writing the library in C++, it would be nice to work with std::function objects instead of function pointers, so I'd like to call functions like this:

using LogFunction = std::function<void(const char*)>;
using DelayFunctionCallback = std::function<void(uint32_t)>;
using DelayFunction = std::function<void(uint32_t, DelayFunctionCallback)>;

void setLogFunction(const LogFunction& logFunction);
void setDelayFunction(const DelayFunction& delayFunction);

Calling the setLogFunction works just fine, but when I try to call setDelayFunctionit doesn't work.

void core_init(log_function_t logFunction, delay_function_t delayFunction)
{
    Utility::getInstance().setLogFunction(logFunction);
    Utility::getInstance().setDelayFunction(delayFunction);
}

It says: Reference to type 'const DelayFunction' (aka 'const function<void (unsigned int, function<void (unsigned int)>)>') could not bind to an lvalue of type 'delay_function_t' (aka 'void(*)(unsigned int, void (*)(unsigned int))')

Obviously I understand why it doesn't work, but I have a feeling that it should be possible to solve and I'm just not experienced enough to solve it.

johnyka
  • 399
  • 3
  • 15

2 Answers2

2

What you're asking seem to be passing a function pointer from C to C++ where the function takes a std::function as argument. I'm afraid this is not possible just as C can't pass a function pointer that takes a std::vector as argument.

When calling Utility::getInstance().setDelayFunction(delayFunction), the ctor of a specialized std::function (i.e. DelayFunction) is matched to construct from a function pointer. However, the match fails because the ctor (of DelayFunction) accepts as its 2nd argument a specialized std::function (i.e. DelayFunctionCallback) , rather than a function pointer (i.e. delay_function_callback_t).

I think the problem lies in the implementation of std::function, which encapsulates the function pointer and erases the latter's type. (See How is std::function implemented?) As a result, a C++ std::function is a different type than a plain-C function pointer.

To workaround this, you could relax the C++-ishness a bit and declare DelayFunction as accepting void(*)(unsigned) instead. I.e., in the C++ file:

using LogFunction = std::function<void(const char*)>;
using DelayFunction = std::function<void(unsigned, delay_function_callback_t)>;
//                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^

EDIT: Re. the comment on calling the DelayFunction object from C++, instead of passing a lamba function as the callback (which would fail with the workaround above, since the lambda function can only construct a DelayFunctionCallback, not a delay_function_callback_t), it might be easier to implement the callback as a static member function and use it directly:

Utility::getInstance().delay(delay, (delay_function_callback_t)&Utility::next);

BTW, if Utility is going to store the std::function objects internally, then it may be more efficient to pass-by-value, since the LogFunction and DelayFunction objects will always be constructed anyway (i.e. they are rvalue in core_init).

Edy
  • 462
  • 3
  • 9
  • Thanks for you answer, but that would take away the main reason I'd like to use std::function instead of function pointer. This is how I'd like to use it: `Utility::getInstance().delay(delay, [this](uint32_t elapsedTime){this->next(elapsedTime);});` It would not be possible to pass the member function as parameter that way, only if I store the instance in a global variable. Guess I will stick with that solution :/ – johnyka Oct 13 '19 at 13:13
  • @johnyka It seems to me that the problem is you are trying to let C pass a function pointer with a `std::function` argument. My guess is it's not possible just as you can't pass a function pointer in C with an `std::vector` argument? – Edy Oct 14 '19 at 00:44
  • Oh damn! Your vector example just made me realize what a dumb thing I'm trying to do :) Yeah, it makes a perfect sense why it's not possible, for some reason it was harder to imagine with a function within a function. I think I can accept your answer as a solution if you update it that there's no way to do this exact thing. – johnyka Oct 14 '19 at 09:37
1

A void(*)() is fundamentally different from a std::function<void()>.

You can get closer with a void(*)( void* ), void*; a std function has both callable-ness and state, a function pointer only has callable-ness. (std function also carries RTTI and how-to-cleanup-state and how-to-copy-state).

Now you can convert a void(*)() into a std function that is stronger; but not the other way. And the arguments to a function are converted the other way when the call happens.

struct callback {
  void* state;
  void(*action)(int32_t);
  void(*cleanup)(void*);
  void*(*copy)(void*);
};

that is the rough C equivalent of a std::function<void(int32_t)>.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524