Reputation: 4678
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);
}
Upvotes: 3
Views: 3284
Reputation: 275966
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()
);
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*)
.
Upvotes: 2
Reputation: 23711
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);
Upvotes: 5