198. Basic Lambda Callback

In embedded drivers, notifying the application about hardware events (such as a button press) typically requires a callback mechanism. Legacy C implementations often rely on function pointers, which can be cumbersome. Modern C++ allows the use of std::function and lambda expressions to define callbacks inline, improving readability and maintainability.

Your task is to implement a class named Button that supports a callback mechanism using modern C++ features.

The Button class must:

  • Contain a private member of type std::function<void()> to store a callback.
  • Provide a method void setCallback(std::function<void()> cb) to register the callback.
  • Provide a method void press() that executes the stored callback only if it is valid.

In the main function, you must register a lambda expression as the callback.
When invoked, this lambda must print:

Action Executed!

followed by a newline.

Program Flow

  1. Instantiate a Button object.
  2. Define a lambda expression that prints "Action Executed!" followed by a newline.
  3. Pass the lambda to button.setCallback(...).
  4. Read an integer N representing the number of commands.
  5. Loop N times:
    • Read a string command.
    • If the command is "PRESS", call button.press().
    • Ignore all other commands.

Input Format

  • First line: Integer N (1 ≤ N ≤ 20)
  • Next N lines: A string command ("PRESS" or any other string)

Input is provided via standard input (stdin).

Output Format

  • For each "PRESS" command, output:

    Action Executed!
    

    followed by a newline.

  • No output is produced for other commands.

Example

Input

2
PRESS
IDLE

Output

Action Executed!

Constraints

  • Must include the <functional> header.
  • Must use a lambda expression in main (no free functions).
  • Callback signature must be void().

 

 

 

Need Help? Refer to the Quick Guide below

In C, callbacks are handled using Function Pointers. This works, but it's rigid—you can't easily pass "state" (variables) into the callback without messy void* casting.

Lambdas (introduced in C++11) are Function Objects that you can write inline. They solve the "State" problem elegantly.

Think of a Lambda as a Disposable Function: You write it right where you need it, it captures the variables it needs from the surrounding scope, executes, and then it's gone.

Syntax & Anatomy

The syntax looks like this: [Captures] (Parameters) { Body }

  • [ ] Capture Clause: "What variables from the outside world do I need?"

  • ( ) Parameters: "What arguments does the caller pass me?" (Just like a normal function).

  • { } Body: The code to execute.

int main() {
    int threshold = 50;

    // A Lambda stored in a variable 'check'
    // It captures 'threshold' so it can use it inside.
    auto check = [threshold](int value) {
        if (value > threshold) {
            printf("Alert! Value %d > %d\n", value, threshold);
        }
    };

    check(60); // Prints "Alert! Value 60 > 50"
}

The Power of Captures ([ ])

This is where Lambdas beat Function Pointers.

  • [ ] (Empty): Captures nothing. Behaves exactly like a standard C function.

  • [=] (By Value): Copies all used local variables into the lambda. Safe, but read-only by default.

  • [&] (By Reference): Accesses the actual variables. Dangerous if the lambda runs after the variable scope ends.

  • [this]: Captures the current object. Essential for using member variables inside a callback.

class Button {
    int id = 1;
public:
    void onClick() {
        // Capture 'this' to access 'id' inside the lambda
        auto callback = [this]() {
            printf("Button %d Clicked\n", this->id);
        };
        callback();
    }
};

Storage & embedded constraints

How do you store a Lambda? This is the tricky part in firmware.

A. The "Captureless" Optimization (Zero Cost)

If a lambda has empty captures [], the compiler treats it identical to a raw function pointer.

  • Cost: Zero RAM overhead. No Heap.

  • Use: Compatible with legacy C drivers expecting void (*ptr)(int).

// Legacy C Driver
void register_irq( void (*cb)(int) );

void setup() {
    // Works perfectly! Decays to function pointer.
    register_irq( [](int code) { 
        printf("IRQ %d\n", code); 
    }); 
}

B. The "Capturing" Challenge (std::function)

If a lambda has captures (e.g., [x]), it is no longer just code; it is an object with data. It won't fit in a raw function pointer.

  • Solution 1 (Standard): Use std::function<void(int)>.

    • Warning: This often uses malloc (Heap) to store the captured variables. Risky in strict embedded.

  • Solution 2 (Template): The "Zero Cost" way for C++.

// Template approach: The compiler generates a custom type for the lambda.
// No Heap. No Virtual calls. Maximum speed.
template <typename Callback>
void run_task(Callback cb) {
    cb();
}

int main() {
    int x = 10;
    // We can capture 'x' without worrying about malloc
    run_task( [x](){ printf("%d", x); } );
}

Relevance in Embedded/Firmware

1. Asynchronous Event Handling

Instead of writing 10 different tiny functions (handleButton1, handleButton2), you define the logic inline during initialization.

button1.onPress( [](){ led.on(); } );
button2.onPress( [](){ motor.stop(); } );

2. Custom Iterators

Running logic over a collection of sensors.

// 'sensors' is an array of objects
std::for_each(sensors.begin(), sensors.end(), [](Sensor& s) {
    s.calibrate(); // Runs for every sensor
});

3. Scoped Locks (RAII)

You can use a lambda to define a critical section that automatically executes logic.

execute_atomic( []() {
    // Interrupts are disabled automatically by the wrapper
    critical_variable++;
}); // Interrupts re-enabled here

Common Pitfalls (Practical Tips)

Pitfall

Details

❌ Dangling Reference

Capturing a local variable by reference [&] and passing the lambda to a Timer/ISR. When the Timer fires later, the function has returned, the stack is gone, and the reference points to garbage. Crash.

❌ Size Overhead

A capturing lambda is an object. If you capture 10 integers [=], the lambda object is 40 bytes. Copying this around takes CPU cycles.

mutable keyword

By default, [=] captures are const (read-only). If you want to modify a captured copy inside the lambda, you must write []() mutable { ... }.

✅ Use auto

Lambda types are unpronounceable compiler-generated secrets. Always use auto to hold them.

Summary Checklist

  • Use [] (empty) if you need compatibility with C function pointers.

  • Use [this] if writing a lambda inside a class method.

  • Avoid [&] (reference capture) for asynchronous tasks (Timers/ISRs).

  • Prefer Templates over std::function to avoid Heap usage.