3

I am trying to pass a lambda as parameter to a function but once I attempt to access a variable inside the lambda which was declared outside, the build fails: error: no matching function for call to 'AWS::subscribe(char [128], mainTask(void*)::<lambda(AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*, void*)>)'

I was thinking that the [&] would take care of capturing variables. I also tried [=] as well as [someVar], [&someVar].

I'm using C++11.

char someVar[128];

aws->subscribe(
  topic,
  [&] (AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData) {
    char *text = (char *)params->payload;
    sprintf(someVar, "%s", text);
  }
);

From the AWS library:

void AWS::subscribe(const char *topic,
                    pApplicationHandler_t iot_subscribe_callback_handler) {
  m_error =
      ::aws_iot_mqtt_subscribe(&m_client, topic, (uint16_t)std::strlen(topic),
                              QOS1, iot_subscribe_callback_handler, NULL);

}
1

2 Answers 2

5

The issue is that the AWS::subscribe function expects a function pointer, not a lambda. Capture-less lambdas can be converted to function pointers, but lambdas with captures (i.e. state) cannot.

You can see the "conventional" solution to this already in the signature: There is a void* parameter that you should pack all your callback-specific data into. Presumably this is the last argument of aws_iot_mqtt_subscribe that you currently set to NULL (prefer using nullptr btw).

This is uglier than using lambdas, but it's basically the only option for C-compatible library interfaces:

// Your callback (could also be a capture-less lambda):
void callbackFunc(/* etc. */, void *pData) 
{
    std::string* someVarPtr = static_cast<std::string*>(pData);
    char *text = (char *)params->payload;
    sprintf(*someVarPtr, "%s", text);
}

// To subscribe:
std::string someVar;
void* callbackData = &someVar; // Or a struct containing e.g. pointers to all your data.
aws_iot_mqtt_subscribe(/* etc. */, callbackFunc, callbackData);
Sign up to request clarification or add additional context in comments.

3 Comments

I see, thanks. Can you recommend a way of tackling this? I guess then the best would be to change the signature of AWS::subscribe to expect a lambda but somehow it transforms it to a function pointer under the hood. So that aws_iot_mqtt_subscribe would "eat" it. Note, that the AWS library is not an official library, it's just a snippet, so it wouldn't be an issue changing it.
@JeJo That's not "conversion" in the C++ sense. Of course you can "convert" it by putting the lambda object itself the void* and having a wrapper function that just calls that. Not to mention the lifetime drawbacks regarding the lambda itself (which must stay in scope until the call happens...).
@MaxLanghof Can you tell me how would it look like with a lambda as data instead of string? Thanks
2
void AWS::subscribe(const char *topic,
                    pApplicationHandler_t iot_subscribe_callback_handler,
                    void* ptr) {
  m_error =
      ::aws_iot_mqtt_subscribe(&m_client, topic, (uint16_t)std::strlen(topic),
                              QOS1, iot_subscribe_callback_handler, ptr);

}

then a little utility type:

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)}; }
}

now we can do:

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);
  }
);
aws->subscribe(
  topic,
  task.get_callback<AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*>(),
  task.get_pvoid()
);

Live example.


As I have learned you can modify AWS class, I'd do this:

template<class Handler>
void AWS::subscribe(const char *topic,
                    Handler iot_subscribe_callback_handler) {
  auto task = utils::c_style_callback(iot_subscribe_callback_handler);

  m_error =
    ::aws_iot_mqtt_subscribe(
      &m_client,
      topic,
      (uint16_t)std::strlen(topic),
       QOS1,
      task.get_callback<AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*>(),
      task.get_pvoid()
    );
}

where subscribe now takes a lambda with signature void(AWS_IoT_Client*, char*, uint16_t, IoT_Publish_Message_Params*).

7 Comments

The only problem here I'm getting various error messages when trying to compile. :-/ utils.cpp:7:7: error: expected primary-expression before 'return' return [](Args...args, void* fptr)->void { and utils.cpp:12:29: error: 'f' was not declared in this scope return std::addressof(f); and few more. Can you check it please? Thanks
@haxpanel 3 typos fixed. The nastiest is a function that returns a function pointer, which is not something I'd usually do directly like that. Live example added with a minimal fake AWS test harness.
I'm still getting the error: no matching function for call to but this time even with a capture-less lambda. I did everything according to your example, and I have no idea what could go wrong.
@haxpanel If the error is in get_callback, the signature of the lambda (and the arguments passed to get_callback) shouldn't have a "trailing void*"; that is added by get_callback. That void* is being used to store the pointer-to-lambda for you.
Removing task.get_pvoid() from where I call subscribe solved this issue! I missed out that the signature of subscribe was different. I'm not quite sure what's going on under the hood, would it be ever useful to pass the address of the function?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.