std::bind

std::bind: Why?

Why? What’s the problem?

Answer:

  • Hard to explain

  • Best to see the problem first

  • Let’s start small, by simple example

Problem: we have …

  • Two dimensional points (x,y)

  • A function to compute the distance between two points

We want:

  • A function to compute the distance from origin (0,0)

What We Have

Point
struct Point
{
    Point(double x, double y)
      : x(x), y(y) {}
    double x, y;
};
Distance
double distance(Point p, Point q)
{
    return std::sqrt(
        std::pow(std::abs(p.x-q.x), 2) +
        std::pow(std::abs(p.y-q.y), 2)
    );
}

Retro C/C++

  • We have all that is needed

  • Could easily define a small function

  • ⟶ Problem solved

  • But this would be soo retro!

Distance from Origin
double distance_origin(Point p)
{
    return distance(p, {0,0});
}

The Real Problem

Nothing is wrong with small functions

  • Compiler will inline them

    • … and optimize away entirely

  • Defined centrally (public header file?) for further reuse

But…

  • What if they serve only one purpose?

Sample Problem

Compute the origin-distances of an array of points, and store those in an equally sized array of double!

Straightforward Implementation

Near the top of the implementation file …

One-Time Function Definition
static double distance_origin(Point p) {
    return distance(p, {0,0});
}

And far down below, in the implementation section …

Location of use
double distances_origin[sizeof(swarm)/sizeof(Point)];
std::transform(swarm, swarm+sizeof(swarm)/sizeof(Point),
               distances_origin,
               distance_origin);

More Sample Problems

Another Sample Problem

Compute the distances of an array of points from a given point, and store those in an equally sized array of double!

Possible solutions: as many as there are different tastes around …

  • Lets write another stupid function, basically a copy of distance_origin - only with (1,1) instead of (0,0)

  • Even better: lets generalize! Functors! Function call operator!

More Straightforward Implementations

One-Time Functor Definition
struct distance_point {
    distance_point(Point origin) : origin(origin) {}
    double operator()(Point p) const {
        return distance(p, origin);
    }
    Point origin;
};
Location of use
double distances_origin[sizeof(swarm)/sizeof(Point)];
std::transform(swarm, swarm+sizeof(swarm)/sizeof(Point),
               distances_point,
               distance_point({1,1}));

Readability

Provided that the helper code is only used once …

  • Readability is inversely proportional to amount of code

  • Number of bugs is directly proportional to amount of code

  • Helper implementation is nowhere near location of use

  • static is the only keyword that enhances readability

Similar problem with many data structures and algorithms …

  • Sorting criteria: std::sort, std::map, …

  • Predicates: std::find_if, std::equal, …

  • Arbitrary adaptations where helper functions are needed

    • Most prominent (although relatively useless nowadays): std::for_each

Introducing std::bind (1)

Best done by example …

void f(int a, int b)
{
    std::cout << a << ',' << b << std::endl;
}
Direct function call
f(1, 2);
prints …
1,2

What if we need the functionality of f(a, b), but are required to pass a callable that takes no parameters?

Introducing std::bind (2)

In other words, we need to create a function-like object that wraps f(a,b) that always calls f with, say, a=1 and b=2.

Hardcoded parameters

auto bound = std::bind(f, 1, 2);
bound();
prints …
1,2
  • Alternative: manually write function adaptor (functor) that remembers parameters until called

  • Origin: Boost (www.boost.org)

Introducing std::bind (3)

Routing parameters into arbitrary positions: std::placeholders

Hardcoding only second parameter

auto bound = std::bind(f, 42, std::placeholders::_1);
bound(7);
prints …
42,7

Exchanging parameters

auto bound = std::bind(f,
       std::placeholders::_2,
       std::placeholders::_1);
bound(1,2);
prints …
2,1

Applying std::bind (1)

So how does this apply to our ``std::transform`` problem?

  • Readability: we want to eliminate those annoying extra helper functions

  • Want to wrap existing double distance(Point, Point) which is similar in purpose but does not fit exactly

What we have …

struct Point {...};
double distance(Point, Point);

What we want …

std::transform(swarm, swarm+sizeof(swarm)/sizeof(Point),
               distances_point,
               SOMETHING WHICH TAKES ONE POINT);

Applying std::bind (2)

Distances from origin
std::transform(swarm, swarm+sizeof(swarm)/sizeof(Point),
               distances_origin,
               std::bind(distance, Point{0,0}, std::placeholders::_1));
Distances from any point
// this is exactly the same as above

Summary

  • Readability: what remains unreadable is only the language itself

  • Have to get used to std::bind

std::bind vs. Lambda

Lambdas are usually a better alternative

std::transform(swarm, swarm+sizeof(swarm)/sizeof(Point),
               distances_origin,
               [](Point p) { return distance({0,0}, p); });

A more advanced exercise

Use std::sort to sort an array of points by their distance to a given point.

A Bigger Picture: Types

What about types?

  • Goal is to have no runtime overhead

  • Late binding (polymorphism) ruled out

  • ⟶ No common base class

  • Only the call signatures (parameter and return types) are the same

What does this mean?

  • Perfect for <algorithm> which is also designed for speed

  • Have to be careful when code size is important

  • Client code has to be instantiated with the type

  • Tradeoff: speed, code size, elegance, design, taste …