sstaub / Ticker

Ticker library for Arduino
MIT License
192 stars 37 forks source link

Add support for std::function (and lambdas) #27

Closed pedvide closed 3 years ago

pedvide commented 3 years ago

Add support for std::function so users can use class members and lambdas as callback. Example (tested on a ESP8266):

#include <Ticker.h>

void func() {
  Serial.println("func.");
}
Ticker ticker1(func, 1000, 0, MILLIS);

// Simple class with (non static) method
class A {
  public:
  A(bool flag) : flag(flag) {}

  void func() {
    Serial.printf("A::func: %s.\n", flag ? "true" : "false");
  }
  bool flag;
};
A a1(true);
// use lambda to capture a1 and execute a1.func()
Ticker ticker2([](){a1.func();}, 1000, 0, MILLIS);
A a2(false);
Ticker ticker3([](){a2.func();}, 1000, 0, MILLIS);

// Class that registers its own method as a callback when it's instantiated.
class B {
  public:
  B(bool flag) : flag(flag), ticker{[this](){this->func();}, 1000, 0, MILLIS} {
    ticker.start();
  }

  void func() {
    Serial.printf("B::func: %s.\n", flag ? "true" : "false");
  }
  bool flag;
  Ticker ticker;
};
B b(true);

// Class that acts like a function (functor)
class C {
  public:
  C(int num) : num(num){}

  // you can call an instance directly with parenthesis and this is executed
  void operator()() const {
    Serial.printf("C(): %d.\n", num);
  }
  int num;
};
C c(4);
Ticker ticker4(c, 1000, 0, MILLIS);

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println();

  ticker1.start();
  ticker2.start();
  ticker3.start();
  ticker4.start();
}

void loop() {
  ticker1.update();
  ticker2.update();
  ticker3.update();
  b.ticker.update();
  ticker4.update();
}
sstaub commented 3 years ago

Which platforms / boards did you checked, with and without functionals?

pedvide commented 3 years ago

So far only on a ESP8266, tomorrow I can test it on a Teensy, which also has functional support. I don't have any other boards.

sstaub commented 3 years ago

Did you test with the __arm__ macro ? I will have a look with the STM32 boards. Because I never worked with <functional> and templates, is it possible to make a callback to a simple function like timer(int value) instead of functions without any parameter? The class example is very complex. Which IDE you use? If it works I will made some additional wishes and will also make a support for the Mbed library.

pedvide commented 3 years ago

Did you test with the arm macro ? I will have a look with the STM32 boards

I tested the __arm__ macro, as far as I could find all arm's have functional, but they also declare it with the __has_include macro, so I prefer to use that.

Because I never worked with and templates, is it possible to make a callback to a simple function like timer(int value) instead of functions without any parameter?

The only difference between std::function and a normal function pointer is to be able to use lambdas so capture context, otherwise it's the same. You can already define typedef void (*fptr)(int); for example, but I don't know how you could use it, maybe by passing a pointer to the argument in the Ticker constructor and calling the function with it? Indeed the nice thing about lambdas is that they allow you to do that, eg:

void func(int a) {
  Serial.println(a);
}

int a = 5;
int* p_a = &a;

Ticker ticker([](){func(*p_a);});

Then you can change a and the function will see the new value via the pointer. (I haven't tested this).

The class example is very complex. Which IDE you use?

I used the Arduino IDE, and tested it on platformIO too. I can remove the B class from the example if it makes it easier.

pedvide commented 3 years ago

I have updated the example above with an other usage of a std::function, a functor, ie: a class that defines a operator() so it can be called like a function.

sstaub commented 3 years ago

Can you download from master branch and test it? I added the example and I'm using arm macro to avoid to much processor specific macros. I'm not using your PR because I made some other improvements.

pedvide commented 3 years ago

Hey, I'll try to test it soon. I had a look at the code and I strongly recommend you don't import the whole std namespace on line 56 (https://github.com/sstaub/Ticker/blob/master/Ticker.h#L56). Just do using fptr = std::function<void()>;, you gain nothing by importing the whole namespace and have a lot to lose if you ever try to define a function or variable that already exists in that namespace. Even worse, any program that uses your library now has imported that namespace, so the potential for conflicts is huge.

Regarding allowing only arm boards to use std::function, it doesn't work for ESP8266, as it's not an arm. It's also kind of risky to assume that any arm board will have functional (probably a very small risk though).

sstaub commented 3 years ago

changing to std::function is ok. Has an ESP32 also a std library?

sstaub commented 3 years ago

I made an update for use with ESP32 and ESP8266, sorry I thought ESPs are also ARMs.

pedvide commented 3 years ago

Great, thank you! I'll close this PR now.

pedrotorchio commented 2 years ago

Hey guys, I'm trying to instantiate a Ticker inside a class constructor, but if I capture the "this" reference in the lamda function, I get the error

no instance of constructor "Ticker::Ticker" matches the argument list: argument types are: (lambda []()->void, int, int, resolution_t)

And if I don't capture the constructor accepts it fine, but then I get this one:

the enclosing-function 'this' cannot be referenced in a lambda body unless it is in the capture listC/C++(1738)

CPP noob here btw, so this is more a coding question than a library question, but since you used my exaact use case in your examples I feel like I should just ask you @pedvide

pedrotorchio commented 2 years ago

Nevermind, seems like this enhancement doesn't work for Arduino UNO. Great library @sstaub! Thank you