Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++/Arduino Pass a function as argument

Tags:

c++

c++11

arduino

I am trying to create a callback style API and am fairly new to C++. I keep getting the error error: invalid use of non-static member function but am not sure the next steps. I would like to have the ability to pass an member function as an argument to another class.

The code is something like

class Button {
  int buttonDownTime = 0;
  int debounceTime = 2000;

  ...

  template<typename Callback>
  void debounce(Callback func) {
    if (millis() - buttonDownTime > debounceTime) {
      func();
    }
  }
}

class Player {
  int playerCount = 0;

  void incriment() {
    playerCount++;
  }
}

void loop() {

  ...

  button.debounce(player.incriment);
}

EDIT:

So I want to thank everyone for the awesome answers so far but I learned something since posting. Arduino's AVR does not include C++'s <functional>. Is this a possible thing to do without that library?

Thank you again!

like image 796
austinbv Avatar asked Jun 19 '17 20:06

austinbv


1 Answers

Non-static member functions require an object to work on, and thus can't be passed and called like normal function pointers.

The simplest way to make your debounce method work, would be to use a lambda that captures your player object and calls increment on it:

class Button {
  //...
  template<typename Callback>
  void debounce(Callback&& func) {  // <<-- Note the && here, without
                                    //      it func would need to be
                                    //      copied
    if (millis() - buttonDownTime > debounceTime) {
      func();
    }
  }
}

void loop() {
  //...
  button.debounce([&player](){ player.incriment(); });
}

With a little bit of extra effort, you could implement something similar to C++17's std::invoke to uniformly invoke any type of callable. Since you're on Arduino and don't have access to the C++ standard library, you'll need to implement std::remove_reference and std::forward yourself as well:

template <typename T>
struct remove_reference
{
  using type = T;
};

template <typename T>
struct remove_reference<T&>
{
  using type = T;
};

template <typename T>
struct remove_reference<T&&>
{
  using type = T;
};

template <typename T>
constexpr T&& forward(typename remove_reference<T>::type& t)
{
  return static_cast<T&&>(t);
}

template <typename T>
constexpr T&& forward(typename remove_reference<T>::type&& t)
{
  return static_cast<T&&>(t);
}

template <typename Callable, typename... Args>
auto invoke(Callable&& func, Args&&... args) 
    -> decltype(forward<Callable>(func)(forward<Args>(args)...))
{
    return forward<Callable>(func)(forward<Args>(args)...);
}

template <typename Callable, typename Class, typename... Args>
auto invoke(Callable&& method, Class&& obj, Args&&... args)
    -> decltype((forward<Class>(obj).*method)(forward<Args>(args)...))
{
    return (forward<Class>(obj).*method)(forward<Args>(args)...);
}

class Button {
  //...
  template<typename Callback, typename... Args>
  void debounce(Callback&& func, Args&&... args) {
    if (millis() - buttonDownTime > debounceTime) {
      invoke(forward<Callback>(func),
             forward<Args>(args)...);
    }
  }
}

void loop() {
  //...
  button.debounce(&Player::increment, player);
}

This doesn't quite do everything that C++17's std::invoke does, but it's close enough to implement a basic callback. It also gives you extra flexibility in that you could pass additional arguments to debounce and they will be passed along to your callback:

void foo(int num) { /*...*/ }

void loop() {
    Button b;
    b.debounce(foo, 42);
}

This doesn't really work if you need to save the callback and call it later, but it doesn't look like that's what you're trying to do.

like image 100
Miles Budnek Avatar answered Oct 17 '22 06:10

Miles Budnek