std::bind
¶
Why? What’s The Problem?¶
Clumsy code like below …
One-time functions, only used to do one stupid little thing (e.g. define
double distance_origin(Point p)
in terms ofdouble distance(Point p, Point q)
)Function definition in places which are far off their call sites ⟶ readability suffers!
Sample Program: All Done Manually¶
#include <cmath>
#include <iostream>
struct Point
{
Point(double x, double y)
: x(x), y(y) {}
double x, y;
};
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)
);
}
double distance_origin(Point p)
{
return distance(p, {0,0});
}
int main()
{
Point points[]{{1,2}, {3,4}, {5,6}};
double origin_distances[sizeof(points)/sizeof(Point)];
// compute origin-distances of points
int i=0;
for (auto p: points)
origin_distances[i++] = distance_origin(p);
for (double dist: origin_distances)
std::cout << dist << std::endl;
return 0;
}
Sideway: std::transform
¶
Problem: manual loops to transform an array (or any container) into something else
Loop body contains transformation code
⟶ cries for duplicate code elimination (here: using std::transform
⟶ similar pattern across
<algorithm>
(see here)Sorting criteria:
std::sort
,std::map
, …Predicates:
std::find_if
,std::equal
, …Arbitrary adaptations where helper functions are needed
Sample Program: Using std::transform
¶
Still using the one-time function double distance_origin(Point p)
though …
#include <cmath>
#include <iostream>
#include <algorithm>
struct Point
{
Point(double x, double y)
: x(x), y(y) {}
double x, y;
};
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)
);
}
double distance_origin(Point p)
{
return distance(p, {0,0});
}
int main()
{
Point points[]{{1,2}, {3,4}, {5,6}};
double origin_distances[sizeof(points)/sizeof(Point)];
// compute origin-distances of points
std::transform(points, points+sizeof(points)/sizeof(Point), origin_distances, distance_origin);
for (double dist: origin_distances)
std::cout << dist << std::endl;
return 0;
}
std::bind
: Null Adaptation, Pointlessly¶
#include <gtest/gtest.h>
#include <functional>
static int void_function()
{
return 42;
}
TEST(bind_suite, plain_void_function)
{
auto func = std::bind(void_function);
ASSERT_EQ(func(), 42);
}
std::bind
: Adapting One Parameter To No Parameter¶
#include <gtest/gtest.h>
#include <functional>
static int one_parameter_function(int i)
{
return i*2;
}
TEST(bind_suite, plain_one_parameter_function)
{
auto func = std::bind(one_parameter_function, 1);
ASSERT_EQ(func(), 2);
}
std::bind
: Hardcoding Parameters¶
#include <gtest/gtest.h>
#include <functional>
static int minus(int i, int j)
{
return i-j;
}
TEST(bind_suite, minus_hardcoded)
{
auto _1_minus_2 = std::bind(minus, 1, 2);
ASSERT_EQ(_1_minus_2(), -1);
}
std::bind
: Swapping Parameters ⟶ std::placeholders
¶
#include <gtest/gtest.h>
#include <functional>
static int minus(int i, int j)
{
return i-j;
}
TEST(bind_suite, minus_swap_parameters)
{
auto second_minus_first = std::bind(minus, std::placeholders::_2, std::placeholders::_1);
ASSERT_EQ(second_minus_first(1, 2), 1);
}
std::bind
: Hardcoding Only First Parameter ⟶ std::placeholders
¶
#include <gtest/gtest.h>
#include <functional>
static int minus(int i, int j)
{
return i-j;
}
TEST(bind_suite, minus_hardcode_first_parameter)
{
auto _42_minus_param = std::bind(minus, 42, std::placeholders::_1);
ASSERT_EQ(_42_minus_param(1), 41);
}
std::bind
: Functor (Is-A Callable)¶
#include <functional>
#include <gtest/gtest.h>
class Functor
{
public:
Functor(int i) : _i{i} {}
int operator()(int addend)
{
return _i + addend;
}
private:
int _i;
};
TEST(bind_suite, functor)
{
Functor func{42};
auto funcfunc = std::bind(func, 1);
ASSERT_EQ(funcfunc(), 43);
}
std::bind
: Lambda (Is-A Callable)¶
#include <functional>
#include <gtest/gtest.h>
TEST(bind_suite, lambda)
{
auto func = [val=42](int i){ return val+i; };
auto funcfunc = std::bind(func, 1);
ASSERT_EQ(funcfunc(), 43);
}
Sample Program: Using std::transform
With std::bind
¶
See how we can eliminate the one-time function double
distance_origin(Point p)
…
#include <cmath>
#include <iostream>
#include <algorithm>
#include <functional>
struct Point
{
Point(double x, double y)
: x(x), y(y) {}
double x, y;
};
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)
);
}
int main()
{
Point points[]{{1,2}, {3,4}, {5,6}};
double origin_distances[sizeof(points)/sizeof(Point)];
// compute origin-distances of points
std::transform(points, points+sizeof(points)/sizeof(Point), origin_distances,
std::bind(distance, std::placeholders::_1, Point{0,0}));
for (double dist: origin_distances)
std::cout << dist << std::endl;
return 0;
}
Summary¶
Readability: what remains unreadable is only the language itself
Have to get used to
std::bind
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
If you want to define interfaces (i.e. share a type), use std::function
What does this mean?
Perfect for
<algorithm>
which is also designed for speedHave to be careful when code size is important
Client code has to be instantiated with the type
Tradeoff: speed, code size, elegance, design, taste …
And Lambdas?
Lambdas are usually a better alternative
⟶ more readable (?)