0

I am having some trouble passing a callback function as an argument to a function.

The function expects the following type:

std::function<void(char* topic, byte* payload, unsigned int length)>

But when I pass this function:

void AMI_MQTT::Callback(char* topic, byte* payload, unsigned int length)

It is not accepted since there is no conversion type from void() to std::function<void()>

My function comes from a class. If I create a separate function in the cpp file (not in the class). It is accepted. Why is my public function from the class not accepted?

J.Meulenbeld
  • 185
  • 1
  • 17
  • 1
    Looks like `void AMI_MQTT::Callback` is a class member function. – πάντα ῥεῖ Aug 31 '22 at 12:16
  • @πάνταῥεῖ That is correct, why would this be an issue compared to a "normal" function with the same arguments – J.Meulenbeld Aug 31 '22 at 12:19
  • 1
    Because you need a class instance of `AMI_MQTT` to call it. You may use a lambda expression instead. – πάντα ῥεῖ Aug 31 '22 at 12:22
  • Here's a solution: https://stackoverflow.com/a/7582576/4641116 – Eljay Aug 31 '22 at 12:23
  • 3
    Member functions aren't regular functions; the type of `&Callback` is `void (AMI_MQTT::*) (char*, byte*, unsigned int)` and it can only be used relative to an instance of `AMI_MQTT`. Pass a lambda function like `[this](char* topic, byte* payload, unsigned int length) { Callback(topic, payload, length); }` instead. – molbdnilo Aug 31 '22 at 12:23

1 Answers1

1

The problem here is that your function appears to be a non-static member function. Non-static member functions implicitly have additional parameter of an instance of the class they belong to, so the signatures of your std::function and the function you are trying to pass simply don't match. However you still can bind a member function to an instance and make it compatible with the signature of a free function if needed:

AMI_MQTT instance;
std::function<void(char* topic, byte* payload, unsigned int length)> func = std::bind(&AMI_MQTT::Callback, instance, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);

Alternatively you may want to get use of type erasure of lambdas:

AMI_MQTT instance;
std::function<void(char* topic, byte* payload, unsigned int length)> func = [instance] (char* topic, byte* payload, unsigned int length) {
    instance.Callback(topic, payload, length);
};

Be advised, however, that instance inside of a lambda is a copy of the instance outside of it, so they don't share the same state.

A way more concise solution would be just making the non-static member function static, in this case you can use it without any instance bound to it:

struct AMI_MQTT {
    static void Callback(char* topic, byte* payload, unsigned int length);
    ...
}

std::function function = AMI_MQTT::Callback;
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
  • `std::bind` is a bad recommendation, it shouldn't be used when we have lambdas. Moreover, it's worth adding that `[instance]` makes a copy of the instance, which might be unexpected. `[&instance]` is the alternative that takes a reference to the object. – Tomasz Kalisiak Aug 31 '22 at 12:41
  • 1
    @TomaszKalisiak `[&instance]` would require the `instance` to align its lifespan with the `func`, which doesn't seem to be the expected behaviour here. But for copy part i agree, it needs to be emphasised. P.S. could you please elaborate why `std::bind` is a bad recommendation? – The Dreams Wind Aug 31 '22 at 12:43
  • 1
    `std::bind` is somewhat obscure, especially this ["If some of the arguments that are supplied in the call to g() are not matched by any placeholders stored in g, the unused arguments are evaluated and discarded."](https://en.cppreference.com/w/cpp/utility/functional/bind) can be rather surprising. Lambdas are safer, simpler, and can do (almost) anything `std::bind` can do – 463035818_is_not_an_ai Aug 31 '22 at 13:43