1

My C++ is very out-of-date. (20+ years, before C++ had closures).

I'm a senior SW engineer, and I spent several hours yesterday trying to figure this out, but I could not work it out.

The background:

I'm trying to create a class, ClosureButton, that is a subclass of another class, ArduinoObject. (An ArduinoObject has setup(), start(), stop(), and loop() methods so that the main program can invoke it.)

(C++ uses the terms closure and lambda to mean different things. In other languages the terms are interchangeable. Forgive me if I'm not using the correct term.)

A ClosureButton's constructor would take a pin number, and a closure with a bool parameter (pressed). I would save that closure to a member variable. When the button's state changed, it would invoke the closure and pass the new button state to the closure.

The question:

I need help with the syntax for the constructor, as well as how to declare the instance variable that would hold the closure.

The constructor would look something like this:

ClosureButton::ClosureButton(uint8_t pin, _unkonwn_syntax_ closure);

(Where I need help with the _unkonwn_syntax_ bit.)

Then I need to declare a member variable on my ClosureButton to hold the closure:

class ClosureButton: ArduinoObject {
  //other stuff
  _other_unkonwn_syntax_ closure;
}

Again, I need help with the syntax to declare a member variable closure of type "closure that takes a bool parameter". (The _other_unkonwn_syntax_ bit.) At this point I don't think my closure will capture variables from its enclosing scope, but at some future date I might need to do that, so help with that would be appreciated as well.)

2
  • C/C++ always had pointers to functions – Juraj Jan 14 '19 at 14:25
  • Yup, I'm aware (and I've always found the syntax for C function pointers to be really hard to decipher. I can figure it out, but not easily.) Are you suggesting I use a C function pointer rather than a closure? – Duncan C Jan 14 '19 at 14:29
1

There is no difference using a function pointer or a simple (non-capturing) lambda (C++ calls them lambdas). The only difference is that you provide a lambda instead of a function pointer.

For example:

void (*func)(bool b);

void setup() {
    func = [](bool b) {
        Serial.println(b);
    };

    Serial.begin(115200);
}

void loop() {
    func(digitalRead(0));
}

It's just a normal function pointer, but instead of assigning a function pointer to it you assign a lambda to it - which is just an anonymous function pointer.

To make life easier you can make a typedef for your function pointer variable type:

typedef void (*buttonCallback)(bool);

Then your functions would look like:

ClosureButton::ClosureButton(uint8_t pin, buttonCallback closure);

and:

class ClosureButton: ArduinoObject {
  //other stuff
  buttonCallback closure;
}

The basic syntax of a lambda is quite straight forward:

[](bool foo, int bar) { ... }
^  ^                    ^
|  |                    +- Function body
|  +- Normal parameter list
+- Empty capture list

If you want to return a value and you don't want to rely on the function being an "auto" return type you can specify the return type after the parameter list:

[](bool foo, int bar)->int { ... }
^  ^                   ^     ^
|  |                   |     +- Function body
|  |                   +- Return type
|  +- Normal parameter list
+- Empty capture list

If you don't specify the return type it is inferred from the type of variable/value that is used with return (as if the function were auto).


Here's a full example passing the lambda to a class which stores the pointer to it in a variable to call later:

typedef int (*callback)(bool b);

class MyClass {
    private:
        callback func;
        int pin;

    public:
        MyClass(int pinno, callback myfunc) {
            func = myfunc;
            pin = pinno;
        }

        void doit() {
            Serial.println(func(digitalRead(pin)));
        }
};

MyClass foo(3, [](bool b)->int {
        Serial.println(b);
        return rand();
});


void setup() {
    pinMode(3, INPUT_PULLUP);
    Serial.begin(115200);
}

void loop() {
    foo.doit();
    delay(100);
}

If you want to do capturing with your lambdas then I'm afraid you're out of luck.

The data type for that kind has to be std::function<void> but unfortunately the cut-down C library AVR-LIBC doesn't support that. The STL is generally not included for small microcontrollers since it uses far too many resources to be practical.

9
  • Ok, that looks like what I need. Thanks. (Using a typedef definitely makes it easier for me to read). I'll have to try that when I get home. – Duncan C Jan 14 '19 at 15:44
  • This won't work for capturing lambdas though. Dealing with those is a whole other kettle of fish. – ratchet freak Jan 14 '19 at 15:54
  • @ratchetfreak I'm unsure what you mean by "capturing" the lambda. You can pass a lambda through a function, assign it to a variable, whatever - exactly the same as a function pointer. – Majenko Jan 14 '19 at 16:02
  • A capturing lambda is a lambda that captures variables. A non-capturing lambda, like the ones in your examples, is just a plain anonymous function, hardly deserving the name “lambda”. – Edgar Bonet Jan 14 '19 at 16:13
  • @EdgarBonet Ah, I see what you mean. I am fairly new to lambdas in C++. – Majenko Jan 14 '19 at 16:34
2

I suggested in a comment that a functor may be a viable alternative to a capturing lambda. This answer is mostly meant to answer the OP's request to elaborate on that suggestion.

Let's define a ButtonCallback as an object that can be called like a function taking a single boolean parameter:

class ButtonCallback {
public:
    virtual void operator()(bool) = 0;
};

To break down the syntax:

  • void operator()(bool) declares a method that allows the object to be called as if it were a function taking a bool argument and returning nothing. In other words, if you issue a call such as my_object(true), that method will be called.
  • virtual _method_declaration_ = 0; says that the method is not going to be defined for this class (it's a “pure virtual method”), but it can be defined by derived classes. A class with such a method is called an “abstract class”: it defines an interface, it cannot be instantiated, and it's meant to be used as a parent for classes that do implement the missing methods.

Now the Button class can be defined like this:

class Button {
public:
    Button(uint8_t pin, ButtonCallback &callback)
    : pin(pin), callback(callback) {}
    void push() {  // for testing only
        Serial.print("press: ");   callback(true);
        Serial.print("release: "); callback(false);
    }
private:
    uint8_t pin;
    ButtonCallback &callback;
};

Notice that the callback is stored as a reference to a ButtonCallback object. The object itself cannot be stored because it has an abstract type. Alternatively, a pointer could be used, which would practically be equivalent.

For using this class, you first have to create the callback as a concrete class that inherits from from ButtonCallback. Any data you wish to capture should be stored as class data:

class CapturingLambda : public ButtonCallback {
public:
    CapturingLambda(int data) : some_data(data) {}
    virtual void operator()(bool pressed) {
        if (pressed)
            Serial.println(some_data);
        else
            Serial.println(0);
    }
private:
    int some_data;
};

With all this in place, the following test:

void setup() {
    Serial.begin(9600);
    int data_to_capture = 42;
    CapturingLambda callback(data_to_capture);
    Button the_button(2, callback);
    the_button.push();
}

void loop(){}

outputs:

press: 42
release: 0

Did you said you “find the syntax of C++ pretty obtuse”? I can only concur with you!


Addendum: Another alternative is to define the callback using the good old C idiom: a “lambda” is the combination of a function pointer and a generic data pointer. The function pointer looks like this:

typedef void (*ButtonCallback)(bool, void *);

The second argument is used to provide the callback whatever data it is supposed to have “captured”. It is a generic pointer (a void *) because the library does not know what kind of data its client may need. Now the Button class has to store both the function pointer and the data pointer, and always provide the data pointer to the callback:

class Button {
public:
    Button(uint8_t pin, ButtonCallback callback, void *callback_data)
    : pin(pin), callback(callback), callback_data(callback_data) {}
    void push() {  // for testing only
        Serial.print("press: ");   callback(true, callback_data);
        Serial.print("release: "); callback(false, callback_data);
    }
private:
    uint8_t pin;
    ButtonCallback callback;
    void *callback_data;
};

The user would have to decide what kind of data it needs, and do explicit pointer casts to/from the proper pointer type and void *. For example, assuming we need an int, the callback may be:

void callback(bool pressed, void *data) {
    int *actual_data = (int *) data;
    if (pressed)
        Serial.println(*actual_data);
    else
        Serial.println(0);
}

Here is the test, with the exact same output as before:

void setup() {
    Serial.begin(9600);
    int data_to_capture = 42;
    Button the_button(2, callback, (void *) &data_to_capture);
    the_button.push();
}

void loop(){}

Notice that, syntactic differences apart, this solution does essentially the same as the previous one. The main difference is that the data is given to the callback as an explicit pointer instead of the implicit this pointer. If you have to choose between these two solutions, I would suggest going for whichever syntax you find easier to work with.

Beware also that in both cases it is the programmer's responsibility to manage the storage of the callback data and make sure the corresponding reference (or pointer) remains valid throughout the lifetime of the Button object.

6
  • Excellent answer. Thank you! (Voted). The one part of it that I couldn’t parse is the constructor of CapturingLambda. Can you explain that bit? The some_data(data) part looks a little like the syntax for invoking a parent class’ constructor, but not quite. – Duncan C Jan 15 '19 at 11:52
  • @DuncanC: It's a member initializer list. some_data(data) means that the some_data class member is initialized with the value of the data constructor parameter. More or less equivalent to writing some_data = data; within the braces. In some cases (not here) an initializer list is the only way to do an initialization. – Edgar Bonet Jan 15 '19 at 12:06
  • Ok, that helps. Why use a member initializer list instead of an explicit constructor that initializes the member variables? And in what cases is a member initializer list the only way to initialize an instance? – Duncan C Jan 15 '19 at 13:22
  • @DuncanC: 1. It doesn't really matter. 2. When invoking the parent class’ constructor and when a member is an object with no default constructor. – Edgar Bonet Jan 15 '19 at 14:31
  • So can you both invoke the parent class' constructor AND use a member initializer list? And if so, what does that syntax look like? – Duncan C Jan 15 '19 at 14:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.