0

What's the proper syntax to accomplish this? The idea is that some object of any class could store a lambda expression in class GuiButton, and then later that call that lambda expression with access to its own local variables.

It should be noted that my platform (Arduino) does NOT support the functional header.

The code I've written to try to express this idea (which does not compile due to the lambda expressions not having access to members of ExampleScreen):

struct GuiButton {
    uint8_t x;  //coordinates for displaying this GUI element
    uint8_t y;
    GuiButton(uint8_t _x, uint8_t _y, void (*_callback)()) :
      x(_x),
      y(_y),
      callback(_callback)
      {};
    virtual void draw(bool _highlight);
  public:
    void (*callback)();   //to be executed BY THE PARENT OBJECT when this element is clicked
};

struct GuiTextButton: public GuiButton {
  char* text;   //text to display in this GUI element
  GuiTextButton(uint8_t _x, uint8_t _y, char* _text, void (*_callback)()) :
    GuiButton(_x, _y, _callback),
    text(_text)
    {};
  void draw(bool _highlight);
};

class ExampleScreen{
  private:
    GuiButton** buttonPtr;
    uint8_t buttonCount;
    uint8_t selectedButton;
    bool proc1Active;
    bool proc2Active;
  public:
    ExampleScreen() : 
      buttonPtr(NULL),
      buttonCount(0),
      selectedButton(0),
      proc1Active(false),
      proc2Active(false)
        {
          //different derived classes of GuiScreen shall have different constructors to define
          //their visual and functional elements
          buttonPtr = new GuiButton* [2];
          buttonCount = 2;
          {
            char text[] = "Button1";
            GuiButton *_thisPtr = new GuiTextButton(5,0,text, []() {
              proc1Active = ~proc1Active;
            });
            buttonPtr[0] = _thisPtr;
          }
          {
            char text[] = "Button2";
            GuiButton *_thisPtr = new GuiTextButton(5,0,text, []() {
              proc2Active = ~proc2Active;
            });
            buttonPtr[2] = _thisPtr;
          }
        };
    void click() {
      void (*callback)() = (buttonPtr[selectedButton]->callback);
      callback();
    };
};

int main() {
  ExampleScreen gui;
  gui.click();
};
Bo Thompson
  • 359
  • 1
  • 12
  • 1
    Your lambdas need to capture `this`. But then they won't be convertible to a plain function pointer. So, everywhere you use `void (*_callback)()` or similar, change it to `std::function _callback` – Igor Tandetnik Jun 14 '20 at 00:41
  • I forgot to mention that I have no access to the `functional` header. Question updated, thank you! – Bo Thompson Jun 14 '20 at 00:44
  • Then you simply can't do it this way. Unfortunately, C++ does not work this way. – Sam Varshavchik Jun 14 '20 at 00:46
  • Then do it the old-fashioned way. Have the callback take a `void*` pointer as a parameter, where context could be passed. Have `GuiButton` take and store both the callback and the context pointer; pass the latter to the former when calling the callback. In `ExampleScreen`, pass `this` as context pointer, have the callback cast it back to `ExampleScreen*` – Igor Tandetnik Jun 14 '20 at 00:48
  • I can't perfectly follow that, but it sure seems reasonable? Could you spell it out more directly as an answer? – Bo Thompson Jun 14 '20 at 00:50

2 Answers2

1

Something along these lines:

class GuiButton {
  GuiButton(void (*_callback)(void*), void* _context)
      : callback(_callback), context(_context) {}

  // Invoke the callback as callback(context)
  void (*callback)(void*);
  void* context;
};

// In ExampleScreen
new GuiButton([](void* context) {
    auto self = static_cast<ExampleScreen*>(context);
    self->proc1Active = ~self->proc1Active;
}, this);
Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
0

Per the comments on your discussion you can't use the functional header which rules out the easy solutions (namely having the callback be a std::function and either capturing the context or using std::bind to bind it.

However, I think you can still do what you want. Make the type of callback be a struct like:

struct CallbackData {
  void (*callback)(ExampleScreen*);
  ExampleScreen* context;
  // obvious constructor here...
}

Then you can call the callback like so:

callback_data.callback(callback_data.context);

And you pass it to the GuiButton constructor like:

new GuiTextButton(5,0,text,CallbackData([](ExampleScreen* e) { ... }, this));

Perhaps a nicer option is to use a functor. To do that you'd create a class like so:

class GuiButtonCallback {
 public:
  GuiButtonCallback(ExampleScreen* context) : context_(context) {}
  void operator() {
    context->proc1Active = ~context->proc1Active;
  }
 private:
  ExampleScreen* context_;
};

And then you can construct things like so:

new GuiTextButton(5 , 0, text, GuiButtonCallback(this));
Oliver Dain
  • 9,617
  • 3
  • 35
  • 48