5.2.23 · D4C++ Programming

Exercises — std - function and std - bind

2,255 words10 min readBack to topic

Throughout, assume:

#include <functional>
#include <iostream>
#include <vector>
using namespace std::placeholders;   // gives us _1, _2, ...

L1 — Recognition

Goal: recognise what fits where, no tracing required.

Exercise 1.1 — Which signature?

You have int add(int a, int b). Fill the blank so this compiles:

std::function<______> f = add;
Recall Solution

Answer: int(int,int). The template parameter of std::function is written like a function type, not like normal template arguments. add returns int and takes two ints, so its call signature is int(int,int). Full line: std::function<int(int,int)> f = add;

Exercise 1.2 — Spot the callable that does NOT fit

Which of these can be stored in std::function<double(double)>?

double sq(double x){ return x*x; }             // A
auto lam = [](double x){ return x + 1.0; };    // B
struct Neg { double operator()(double x) const { return -x; } }; // C
int badcount(int a, int b){ return a+b; }      // D
Recall Solution

A, B, C fit. D does not. std::function<double(double)> accepts anything callable as one double in, one double out.

  • sq — matches exactly.
  • lam — a lambda taking one double, returning double.
  • Neg{} — a functor whose operator() matches.
  • badcount takes two ints — wrong argument count, so it cannot be wrapped. std::function checks the call shape, and the shape is wrong.

L2 — Application

Goal: write the correct expression yourself.

Exercise 2.1 — Store three callables in one vector

Write code that puts these three tasks into a single std::vector and runs them in order:

  • a lambda printing "boot",
  • a lambda printing "load",
  • a free function void shutdown() that prints "halt".
Recall Solution
void shutdown(){ std::cout << "halt\n"; }
 
std::vector<std::function<void()>> tasks;
tasks.push_back([]{ std::cout << "boot\n"; });
tasks.push_back([]{ std::cout << "load\n"; });
tasks.push_back(shutdown);
for (auto& t : tasks) t();

Output:

boot
load
halt

Why this works: each lambda has its own unique unnamed type, so a vector of lambdas is impossible directly. std::function<void()> gives all three the same type based only on the signature void().

Exercise 2.2 — Fix one argument with bind

Given int power(int base, int exp) (computes base^exp), build a callable square such that square(n) returns n*n, using std::bind.

Recall Solution
int power(int base, int exp){
    int r = 1;
    for (int i = 0; i < exp; ++i) r *= base;
    return r;
}
auto square = std::bind(power, _1, 2);   // exp fixed = 2, base = _1
// square(5) -> power(5, 2) = 25

Why: we want the base to come from the caller and the exponent to be locked at 2. So base becomes the placeholder _1 (the first call argument) and exp is the fixed value 2. square(5) expands to power(5, 2) = 25.


L3 — Analysis

Goal: trace exactly what runs; find the bug.

Exercise 3.1 — Predict the output (reordering)

int sub(int a, int b){ return a - b; }
auto g = std::bind(sub, _2, _1);
std::cout << g(3, 10) << "\n";

What prints?

Figure — std - function and std - bind
Recall Solution

Prints 7. Read _n as "the n-th argument the caller supplies". The caller supplies (3, 10), so _1 = 3 and _2 = 10.

  • First bind slot is _2 → goes into aa = 10.
  • Second bind slot is _1 → goes into bb = 3.

So g(3,10) becomes sub(10, 3) = 7. Look at the figure: the crossing arrows are the whole point — _2 and _1 swap the caller's inputs before they reach sub.

Exercise 3.2 — The copy-vs-reference bug

struct Accumulator {
    int total = 0;
    void add(int x){ total += x; }
};
Accumulator acc;
auto f = std::bind(&Accumulator::add, acc, _1);  // note: acc, not &acc
f(5);
f(5);
std::cout << acc.total << "\n";

What prints, and why is it surprising?

Recall Solution

Prints 0. std::bind copies its arguments by value. Writing acc (not &acc) hands bind a copy of the accumulator. Every call to f mutates that hidden internal copy, never the original acc. So acc.total stays 0. Two correct fixes:

auto f = std::bind(&Accumulator::add, &acc, _1);        // pass a pointer
auto g = std::bind(&Accumulator::add, std::ref(acc), _1); // pass a reference wrapper

Either way, calls now reach the real acc, and it would print 10. (See std::ref and std::cref.)

Exercise 3.3 — The empty-function crash

std::function<int(int)> f;   // never assigned
std::cout << f(42) << "\n";

What happens at runtime?

Recall Solution

It throws std::bad_function_call (the program terminates unless you catch it). An empty std::function holds no target, so there is nothing to invoke. Fix — always guard:

if (f) std::cout << f(42) << "\n";
else   std::cout << "no target\n";

if (f) is true only when the std::function currently holds a callable.


L4 — Synthesis

Goal: combine the pieces into a small working design.

Exercise 4.1 — A tiny event dispatcher

Build a class Button that stores a list of void() callbacks (onClick), lets you register more, and fires them all with click(). Then register two lambdas and one bound member function Logger::log.

Recall Solution
struct Logger {
    void log() const { std::cout << "logged\n"; }
};
 
struct Button {
    std::vector<std::function<void()>> handlers;
    void onClick(std::function<void()> h){ handlers.push_back(std::move(h)); }
    void click(){ for (auto& h : handlers) h(); }
};
 
int main(){
    Logger lg;
    Button b;
    b.onClick([]{ std::cout << "sound\n"; });
    b.onClick([]{ std::cout << "ripple\n"; });
    b.onClick(std::bind(&Logger::log, &lg)); // bound member fn
    b.click();
}

Output:

sound
ripple
logged

Why the design works: std::function<void()> is the uniform "shape" every handler agrees on. Lambdas, free functions, and bound member functions all collapse into that one type, so they live happily in the same vector. This is exactly the Callbacks and Event Systems pattern. (Note &Logger::log needs an object — here the pointer &lg — as its first bound argument, since a member function secretly receives this first.)

Exercise 4.2 — Compose bind + placeholders to make a "clamp lower bound"

Given int maxOf(int a, int b){ return a>b ? a : b; }, build atLeast5 such that atLeast5(x) returns x if x >= 5, else 5.

Recall Solution
auto atLeast5 = std::bind(maxOf, _1, 5);  // a = caller's x, b = 5
// atLeast5(3) -> maxOf(3,5) = 5
// atLeast5(9) -> maxOf(9,5) = 9

Why: max(x, 5) is exactly "never below 5". We route the caller's argument into a (via _1) and lock the floor 5 into b. Both orderings (_1,5 or 5,_1) give the same answer here because max is symmetric — a nice self-check that you understood the mapping.


L5 — Mastery

Goal: reason about cost, alternatives, and subtle semantics.

Exercise 5.1 — Cost reasoning

Your inner loop calls a callback 10 million times. Version A takes std::function<int(int)>; version B is template<class F> ... F f. Which is faster, and why — in one precise sentence about why the compiler can/can't help?

Recall Solution

Version B (the template) is faster. A template instantiates with the callable's concrete type, so the compiler sees the exact code at the call site and can inline it. std::function hides the concrete type behind type erasure (see Templates and Type Erasure): the call goes through an indirect / virtual-like dispatch that cannot be inlined, and constructing it may heap-allocate for large targets. So std::function trades a small runtime cost for the ability to store unknown types uniformly — use it only when you actually need that uniformity.

Exercise 5.2 — bind vs lambda equivalence

Rewrite std::bind(sub, _2, _1) (from Exercise 3.1) as a modern lambda, and state one reason lambdas are usually preferred.

Recall Solution
auto g = [](int x, int y){ return sub(y, x); };   // g(3,10) -> sub(10,3) = 7

Reason preferred: the lambda spells out the argument routing directly in code you can read (sub(y, x)), whereas _2, _1 forces the reader to mentally decode placeholder positions. Lambdas are also easier for the compiler to inline and don't drag in std::bind's machinery. See Lambda Expressions vs Function Pointers for the wider callable landscape.

Exercise 5.3 — Predict the trickiest output

int f(int a, int b, int c){ return a*100 + b*10 + c; }
auto h = std::bind(f, _3, _1, 7);
std::cout << h(2, 99, 5) << "\n";

What prints? (Watch every slot.)

Figure — std - function and std - bind
Recall Solution

Prints 527. Caller supplies (2, 99, 5)_1 = 2, _2 = 99, _3 = 5. Map each of f's parameters (a, b, c) to its bind slot:

  • a_35
  • b_12
  • c ← fixed 7

So the actual call is f(5, 2, 7) = 5*100 + 2*10 + 7 = 527. Notice _2 = 99 is never used — placeholders let you drop call arguments too. The figure shows the wiring: three arrows in, but the 99 wire simply dead-ends.


Recall Self-test summary

Placeholder _n means what? ::: The n-th argument supplied when the bound object is later called. How do you keep a live link to an object through std::bind? ::: Pass a pointer &obj or wrap in std::ref(obj); plain bind copies by value. What does calling an empty std::function do? ::: Throws std::bad_function_call; guard with if (f). Why is a template callback usually faster than std::function? ::: The template keeps the concrete type, so the call can be inlined; std::function erases the type and dispatches indirectly.