Step 7 of 7
Challenge: callbacks with std::function
Sometimes you want to store "something to call later": a button's click handler, a callback when a download finishes, a list of listeners. In C that's a function pointer plus a void * for extra data. C++ has a type-safe version: std::function<R(Args...)> (from <functional>) holds any callable with that signature: a plain function, a lambda (including one with captures), or an object with operator().
#include <functional>
#include <iostream>
#include <string>
#include <vector>
int twice(int x) { return 2 * x; }
int main() {
std::vector<std::function<int(int)>> steps;
int offset = 100;
steps.push_back(twice); // a plain function
steps.push_back([](int x) { return x + 1; }); // a lambda
steps.push_back([offset](int x) { return x + offset; }); // a lambda with a capture
int value = 5;
for (const auto& f : steps) value = f(value);
std::cout << value << "\n";
std::function<void(const std::string&)> on_done;
if (!on_done) std::cout << "no handler yet\n";
int calls = 0;
on_done = [&calls](const std::string& file) { calls++; std::cout << "got " << file << "\n"; };
on_done("a.zip");
on_done("b.zip");
std::cout << calls << " calls\n";
}
111
no handler yet
got a.zip
got b.zip
2 calls
How it works
- The type in the angle brackets is a function signature:
int(int)takes an int and returns an int;void(const std::string&)takes a string and returns nothing. - Different kinds of callables can live in the same vector, as long as they match the signature.
- An empty
std::functionconverts tofalse. Calling an empty one is an error, so check first when it might be unset.
Designing the event bus
Each subscription needs three things: its id, its event name, and its handler. A small struct in a std::vector keeps them in registration order, which is the order emit must use. Keep a counter member for the next id, starting at 1.
onappends a subscription and returns the new id.offfinds the id and erases it (returnfalseif it isn't there).emitloops over the subscriptions, calls the handlers whose event name matches, and counts them.
Your task
Build an EventBus where handlers subscribe to named events:
int on(const std::string& event, std::function<void(const std::string&)> fn)registers a handler and returns a unique id (1, 2, 3, ...)bool off(int id)unregisters; returns false if the id isn't registeredint emit(const std::string& event, const std::string& payload)calls every handler for that event, in registration order, and returns how many ran
Challenge 2
Build a command registry. add(name, fn) stores a std::function<std::string(const std::vector<std::string>&)>. run(line) splits the line on spaces, calls the command named by the first word with the remaining words, and returns its result. An unknown command returns unknown command: NAME, and an empty line returns "".
Challenge 3
Write std::function<int(int)> compose(std::function<int(int)> f, std::function<int(int)> g) returning a function that runs f first and then g, so compose(f, g)(x) is g(f(x)). Then write std::function<int(int)> repeat_fn(std::function<int(int)> f, int n) that applies f to its input n times (0 times returns the input unchanged).