4

I'd like to pass a capture-ful lambda function to a C-style callback with a help of a utility function:

#include <utility>
#include <iostream>
#include <array>

struct AWS_IoT_Client {};
struct IoT_Publish_Message_Params {
    int payload[1024];
};

typedef enum {
    SHADOW_ACK_TIMEOUT, SHADOW_ACK_REJECTED, SHADOW_ACK_ACCEPTED
} Shadow_Ack_Status_t;

typedef enum {
    SHADOW_GET, SHADOW_UPDATE, SHADOW_DELETE
} ShadowActions_t;

typedef void (*pApplicationHandler_t)(AWS_IoT_Client *pClient, char *pTopicName, uint16_t topicNameLen,
                                      IoT_Publish_Message_Params *pParams, void *pClientData);

typedef void (*fpActionCallback_t)(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status,
                                   const char *pReceivedJsonDocument, void *pContextData);

struct AWS {
    inline void subscribe(char*, pApplicationHandler_t, void*)
    {
    }

    inline void get_shadow(fpActionCallback_t, void *)
    {
    }
};

AWS* aws = new AWS;

    namespace utils {
      template<class F>
      struct c_style_callback_t {
        F f;
        template<class...Args>
        static void(*get_callback())(Args..., void*) {
          return [](Args...args, void* fptr)->void {
            (*static_cast<F*>(fptr))(std::forward<Args>(args)...);
          };
        }
        void* get_pvoid() {
          return std::addressof(f);
        }
      };
      template<class F>
      c_style_callback_t< std::decay_t<F> >
      c_style_callback( F&& f ) { return {std::forward<F>(f)}; }
    }

int main() {
    char someVar[1024]={0};

    auto task2 = utils::c_style_callback(
      [&] (const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status,
           const char *pReceivedJsonDocument, void *pContextData) {
        //sprintf(someVar, "%s", text);
      }
    );

    aws->get_shadow(
      task2.get_callback<const char*, ShadowActions_t, Shadow_Ack_Status_t, const char*, void*>(),
      task2.get_pvoid()
    );

    auto task = utils::c_style_callback(
      [&] (AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, IoT_Publish_Message_Params *params) {
        char *text = (char *)params->payload;
        sprintf(someVar, "%s", text);
      }
    );

However, I'm not getting error with a different callback:

    char topic[] = "some topic";
    aws->subscribe(
      topic,
      task.get_callback<AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*>(),
      task.get_pvoid()
    );
}

Error message:

error: cannot initialize a parameter of type 'fpActionCallback_t' (aka 'void (*)(const char *, ShadowActions_t, Shadow_Ack_Status_t, const char *, void *)') with an rvalue of type 'void (*)(const char *, ShadowActions_t, Shadow_Ack_Status_t, const char *, void *, void *)': different number of parameters (5 vs 6)
      task2.get_callback<const char*, ShadowActions_t, Shadow_Ack_Status_t, const char*, void*>()`

I don't get why.

live example

Update:

I would emphasize that the utility function c_style_callback can be very useful for others who encounter this issue in the future. It is a more flexible solution than the one in the answer here as this one supports custom callback signature (not sure how to describe it).

My question was more about using it than the question about "lambda with captures as a function pointer".

underscore_d
  • 6,309
  • 3
  • 38
  • 64
haxpanel
  • 4,402
  • 4
  • 43
  • 71
  • 1
    Possible duplicate of [C++ lambda with captures as a function pointer](https://stackoverflow.com/questions/7852101/c-lambda-with-captures-as-a-function-pointer) – Scheff's Cat Sep 11 '18 at 09:15

1 Answers1

2

Look at your callback definitions:

typedef void (*pApplicationHandler_t)(AWS_IoT_Client *pClient, char *pTopicName, uint16_t topicNameLen, IoT_Publish_Message_Params *pParams, void *pClientData);

typedef void (*fpActionCallback_t)(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, const char *pReceivedJsonDocument, void *pContextData);

and the callback params in each case:

task.get_callback<AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*>()

task2.get_callback<const char*, ShadowActions_t, Shadow_Ack_Status_t, const char*, void*>()

In the first case you have an additional parameter void* pClientData, in the latter case you don't, even though get_callback will return a lambda with that additional parameter, because you return return [](Args...args, void* fptr), not return [](Args...args).

So either change your 2nd typedef to this:

typedef void (*fpActionCallback_t)(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, const char *pReceivedJsonDocument, void *pContextData, void *pClientData);

or change the return type of get_callback.

Max Vollmer
  • 8,412
  • 9
  • 28
  • 43