199. Stateful Lambda Counter

In firmware systems, callbacks often need to maintain their own internal state (for example, counting error pulses, debouncing signals, or tracking retry attempts) without relying on global variables. Using static variables inside functions makes such logic hard to reset, reuse, or test. A stateful lambda provides a clean and encapsulated solution.

Your task is to implement a class Ticker that stores a std::function<void()> callback. In main, you must register a lambda expression with this Ticker that:

  • Maintains an internal integer counter starting at 0
  • Uses lambda capture initialization to store the counter
  • Is marked mutable so it can modify its internal state
  • Increments the counter and prints the updated value on each execution

⚠️ Language Requirement:
This problem requires C++14 or later, due to the use of lambda capture initialization ([count = 0]).

Program Flow:

  1. Instantiate the Ticker class
  2. Register a lambda callback that initializes and maintains an internal counter
  3. Read an integer N
  4. Loop N times:
    • Read a string command cmd
    • If cmd is "TICK", invoke the callback using ticker.tick()

Input Format:

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

Input is provided via standard input (stdin).

Output Format:

  • For every "TICK" command, print:

    Count: <new_value> 
  • Each output must be on a new line
  • If there are no "TICK" commands, produce no output

Example:

Input:

3
TICK
WAIT
TICK

Output:

Count: 1 
Count: 2 

Constraints:

  • Must use a mutable lambda
  • Must not use global variables
  • Must not use static variables inside the lambda
  • Must compile with C++14 or later

 

 

 

 

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.