C++ std::bind: Placeholders, Partial Application, and When a Lambda Is Better
Key takeaways
std::bind is a function introduced in C++11 that creates a new function object by pre-binding a function and its arguments. It is used for partial application, argument relocation, member function binding, etc.
What is bind?
std::bind is a function introduced in C++11 that creates a new function object by prebinding a function and its arguments. Used for partial application, argument relocation, member function binding, etc.
Why do you need it?:
- Partially applied: Some parameters are fixed in advance
- Argument Relocation: Change the order of arguments
- Member function: Use member functions like regular functions
- Callback: Create a callback function
// ❌ Hand-written: tedious
class Add5 {
int fixed_;
public:
Add5(int fixed) : fixed_(fixed) {}
int operator()(int x) const { return fixed_ + x; }
};
Add5 add5(5);
add5(10); // 15
// ✅ bind: concise
auto add5 = std::bind(add, 5, std::placeholders::_1);
add5(10); // 15
std::bind arrived in C++11 specifically to replace this pattern — writing a whole class just to fix one argument of add was common enough in pre-C++11 code (especially adapting free functions for STL algorithms that expect a unary predicate) that it was worth a standard library facility. In modern code, a lambda does the same job with less ceremony and clearer types, which is why bind’s practical role today has narrowed mostly to legacy codebases and a few specific member-function-binding cases covered below.
Fixing Arguments with std::bind
#include <functional>
using namespace std;
using namespace placeholders;
int add(int a, int b) {
return a + b;
}
int main() {
// Partial application
auto add5 = bind(add, 5, _1);
cout << add5(10) << endl; // 15
cout << add5(20) << endl; // 25
}
How bind works:
// Conceptual implementation
template<typename Func, typename... BoundArgs>
class BindExpression {
Func func_;
std::tuple<BoundArgs...> boundArgs_;
public:
BindExpression(Func func, BoundArgs... args)
: func_(func), boundArgs_(args...) {}
template<typename... CallArgs>
auto operator()(CallArgs&&... args) {
// Combine boundArgs_ with the call-site args and invoke func_
return std::apply(func_, /* combined arguments */);
}
};
This sketch is deliberately simplified, but it captures the real mechanism: bind doesn’t call anything at the point it’s invoked — it stores the function and the bound arguments (including placeholders as markers) inside an object, and the actual call happens later, when that object’s operator() runs with the call-site arguments. That deferred-call design is exactly what makes add5 reusable — it’s a value you can pass around, store, and invoke multiple times with different trailing arguments, not a one-shot expression.
Reordering Arguments with Placeholders
int subtract(int a, int b) {
return a - b;
}
int main() {
// Arguments in original order
auto f1 = bind(subtract, _1, _2);
cout << f1(10, 3) << endl; // 7
// Arguments swapped
auto f2 = bind(subtract, _2, _1);
cout << f2(10, 3) << endl; // -7 (3 - 10)
// Fixed argument
auto f3 = bind(subtract, 100, _1);
cout << f3(30) << endl; // 70
}
_1 and _2 refer to positions in the call, not positions in the bound expression — f2(10, 3) passes 10 as the value that fills _1 and 3 as the value that fills _2, and bind(subtract, _2, _1) says “call subtract with the second call argument first, then the first call argument.” Reading bind(subtract, _2, _1) at a glance and correctly predicting f2(10, 3) evaluates to subtract(3, 10) rather than subtract(10, 3) is exactly the kind of mental indirection that makes bind expressions harder to review than the equivalent lambda [](int a, int b) { return subtract(b, a); }.
Binding Member Functions and Object Pointers
class Calculator {
public:
int multiply(int a, int b) const {
return a * b;
}
int value = 10;
};
int main() {
Calculator calc;
// Binding a member function
auto f = bind(&Calculator::multiply, &calc, _1, _2);
cout << f(3, 4) << endl; // 12
// Binding a member variable
auto getValue = bind(&Calculator::value, &calc);
cout << getValue() << endl; // 10
}
This is one of the cases where bind still earns its keep over a lambda for some codebases: &Calculator::multiply is a pointer-to-member-function, which needs an object instance to be called through ((calc.*ptr)(3, 4) is the raw syntax) — bind’s special-cased handling of member pointers as the first bound argument hides that syntax behind an ordinary-looking function call. The equivalent lambda, [&calc](int a, int b) { return calc.multiply(a, b); }, is arguably just as clear and doesn’t require knowing bind’s member-pointer convention, which is why many style guides now prefer it even here.
Event Handlers, Partial Application, Filters, and Callbacks
Event handler
class Button {
private:
function<void()> onClick;
public:
void setOnClick(function<void()> handler) {
onClick = handler;
}
void click() {
if (onClick) {
onClick();
}
}
};
class App {
public:
void handleClick(const string& buttonName) {
cout << buttonName << " clicked" << endl;
}
};
int main() {
App app;
Button btn;
// Binding a member function as the callback
btn.setOnClick(bind(&App::handleClick, &app, "Button1"));
btn.click(); // "Button1 clicked"
}
Binding &app by raw pointer here is worth flagging as a real hazard, not just a style nitpick: the bound callable stores a copy of that pointer, and nothing about bind keeps app alive. If btn.setOnClick(...) outlived app — say, app were a local variable that went out of scope while btn persisted — calling btn.click() afterward would invoke a member function through a dangling pointer, which is undefined behavior with no compiler warning to catch it. This is exactly the class of bug that motivates preferring shared_ptr-based ownership or a lambda that captures a weak_ptr for any callback whose lifetime isn’t obviously tied to its target.
Partial application
int power(int base, int exponent) {
int result = 1;
for (int i = 0; i < exponent; i++) {
result *= base;
}
return result;
}
int main() {
// Squaring function
auto square = bind(power, _1, 2);
cout << square(5) << endl; // 25
// Cubing function
auto cube = bind(power, _1, 3);
cout << cube(5) << endl; // 125
// Powers of 2
auto powerOf2 = bind(power, 2, _1);
cout << powerOf2(10) << endl; // 1024
}
square and powerOf2 both come from the same two-argument power function, just fixing a different argument in each case — this is the textbook definition of partial application, and it’s genuinely useful for building a family of related single-argument functions from one general-purpose implementation without writing squareOf(x)/cubeOf(x)/powerOfTwo(x) as separate hand-written functions.
Filter Combination
bool inRange(int value, int min, int max) {
return value >= min && value <= max;
}
int main() {
vector<int> v = {1, 5, 10, 15, 20, 25, 30};
// Filter for the 10-20 range
auto filter = bind(inRange, _1, 10, 20);
auto it = find_if(v.begin(), v.end(), filter);
if (it != v.end()) {
cout << "First match: " << *it << endl; // 10
}
// Find all matches
for (int x : v) {
if (filter(x)) {
cout << x << " "; // 10 15 20
}
}
}
Binding 10 and 20 into inRange produces exactly the unary predicate std::find_if expects — this is the same pattern the STL algorithms were designed around in the pre-lambda era, and it’s still a legitimate use even in modern code, though [](int x) { return x >= 10 && x <= 20; } reads just as clearly without needing the reader to know what _1 refers to.
Callback system
class Timer {
private:
function<void()> callback;
public:
void setCallback(function<void()> cb) {
callback = cb;
}
void trigger() {
if (callback) {
callback();
}
}
};
class Logger {
public:
void log(const string& level, const string& message) {
cout << "[" << level << "] " << message << endl;
}
};
int main() {
Timer timer;
Logger logger;
// Partial application of a member function
timer.setCallback(bind(&Logger::log, &logger, "INFO", "Timer fired"));
timer.trigger(); // [INFO] Timer fired
}
The same dangling-pointer caution from the event handler example applies here too — &logger is captured by raw pointer inside the bound callable stored in timer, so timer must never outlive logger. It’s easy to miss this in a larger program where Timer and Logger are constructed far apart in the code, which is one more reason bind-based callbacks deserve extra scrutiny in code review around object lifetimes.
bind vs lambda
// bind
auto f1 = bind(add, 5, _1);
// lambda (clearer)
auto f2 = [](int x) { return add(5, x); };
int main() {
cout << f1(10) << endl; // 15
cout << f2(10) << endl; // 15
}
Lambda Advantages:
- Easier to read
- Type inference
- Clear compilation errors
bind advantages:
- Argument relocation
- Simple member pointer
The “clear compilation errors” point for lambdas is worth expanding on, because it’s often the deciding factor in practice: a type mismatch inside a lambda body produces a compiler error pointing at the actual line where the mismatch occurs, the same as any normal function. A type mismatch inside a bind expression frequently surfaces as a wall of template instantiation errors from deep inside <functional>’s internals, because bind’s implementation is itself a heavily templated forwarding mechanism — diagnosing which bound argument or placeholder caused the problem often means reading through several layers of template error messages that don’t mention your code at all.
Reference Binding, Placeholder Order, and Nested bind
Reference binding
int x = 10;
// ❌ Copies x
auto f1 = bind(add, x, _1);
x = 20;
cout << f1(5) << endl; // 15 (x was copied as 10)
// ✅ Binds a reference
auto f2 = bind(add, ref(x), _1);
x = 20;
cout << f2(5) << endl; // 25 (reads the current x, 20)
This default-to-copy behavior surprises people coming from languages where variables are captured by reference by default — bind’s bound arguments are stored by value unless explicitly wrapped in std::ref/std::cref, which is a deliberate design choice to avoid accidentally storing a reference to something that might go out of scope. The tradeoff is that “I want this bound value to track updates to the original variable” needs the explicit ref() wrapper, or the behavior silently becomes “a snapshot from when bind was called” instead.
Placeholder order
// ❌ Confusing
auto f = bind(subtract, _2, _1); // arguments swapped
cout << f(10, 3) << endl; // -7 (3 - 10)
// ✅ Lambda (clear)
auto f2 = [](int a, int b) { return subtract(b, a); };
cout << f2(10, 3) << endl; // -7
Nested bind
// ❌ Complicated
auto f = bind(add, bind(multiply, _1, 2), _2);
// ✅ Lambda (clear)
auto f2 = [](int x, int y) { return add(multiply(x, 2), y); };
Nesting bind calls composes functions, but the placeholder numbering has to be tracked across both levels simultaneously — in bind(add, bind(multiply, _1, 2), _2), both _1 and _2 refer to positions in the outer call, and the inner bind(multiply, _1, 2) is evaluated first to produce the first argument to add. This is exactly the kind of nested indirection that scales badly past two levels; the lambda version stays equally readable at any depth of composition because it’s just ordinary function-call syntax.
Comparators, Thread Callbacks, and Function Adapters
Customizing the comparison function
struct Person {
std::string name;
int age;
};
bool compareByAge(const Person& a, const Person& b) {
return a.age < b.age;
}
bool compareByName(const Person& a, const Person& b) {
return a.name < b.name;
}
// Usage
std::vector<Person> people = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}
};
// Sort by age
std::sort(people.begin(), people.end(), compareByAge);
// Sort by name
std::sort(people.begin(), people.end(), compareByName);
This particular example doesn’t actually use bind at all — compareByAge and compareByName are plain free functions passed directly to std::sort, which is the simplest possible comparator pattern and needs no partial application. It’s included here as the baseline: bind (or a lambda) only becomes necessary once the comparator needs extra fixed context beyond the two elements being compared, such as sorting by a field chosen at runtime.
Thread callback
class Worker {
public:
void process(int id, const std::string& task) {
std::cout << "Worker " << id << ": " << task << '\n';
}
};
// Usage
Worker worker;
// Pass a member function to a thread
std::thread t1(std::bind(&Worker::process, &worker, 1, "Task A"));
std::thread t2(std::bind(&Worker::process, &worker, 2, "Task B"));
t1.join();
t2.join();
std::thread’s constructor actually accepts any callable directly, including a pointer-to-member-function plus its object and arguments (std::thread(&Worker::process, &worker, 1, "Task A") works with no bind at all) — std::thread performs the same kind of binding internally that std::bind does explicitly here. Wrapping it in bind is redundant in this specific case, though it does make the intent (“this is a fully-prepared callable being handed to the thread”) slightly more explicit to a reader.
Function adapter
int divide(int a, int b) {
return a / b;
}
// Reversing argument order
auto divideBy = [](int divisor) {
return std::bind(divide, std::placeholders::_1, divisor);
};
// Usage
auto divideBy2 = divideBy(2);
auto divideBy10 = divideBy(10);
std::cout << divideBy2(100) << '\n'; // 50
std::cout << divideBy10(100) << '\n'; // 10
This factory-function pattern — a lambda that returns a bind expression — is a legitimate way to build a family of adapters at runtime from a parameter (divisor) that isn’t known until the factory is called. It’s worth noting the outer lambda could just as easily return another lambda instead of a bind expression (return [divisor](int x) { return divide(x, divisor); };), which avoids mixing the two styles and keeps the whole chain in one idiom.
FAQ
Q1: When do you use bind?
A:
- Partially applied: Some parameters are fixed in advance
- Member function binding: Use member functions like regular functions
- Argument Relocation: Change the order of arguments
// Partial application
auto add5 = std::bind(add, 5, std::placeholders::_1);
// Member function
auto f = std::bind(&Calculator::multiply, &calc, std::placeholders::_1, std::placeholders::_2);
Q2: bind vs lambda?
A: Most lambdas are clearer. bind is only used in special cases.
// bind: more indirect
auto f1 = std::bind(add, 5, std::placeholders::_1);
// lambda: clear (recommended)
auto f2 = [](int x) { return add(5, x); };
Why Lambda is recommended:
- Easier to read
- Clear type inference
- Compilation errors are clear
Q3: What is the performance overhead?
A: Almost no overhead due to inlining.
auto f = std::bind(add, 5, std::placeholders::_1);
f(10); // the compiler inlines this down to add(5, 10)
This holds for direct, non-virtual calls the compiler can see through — the moment a bind expression is stored inside a type-erased std::function, that inlining guarantee weakens, since the compiler can no longer see through the type erasure to the concrete bound expression at the call site. For hot paths, prefer auto or a template parameter over std::function if you want to keep the zero-overhead property bind alone provides.
Q4: How do I bind references?
A: Use std::ref() or std::cref().
int x = 10;
// ❌ Copies x
auto f1 = std::bind(add, x, std::placeholders::_1);
x = 20;
f1(5); // 15 (x was copied as 10)
// ✅ Binds a reference
auto f2 = std::bind(add, std::ref(x), std::placeholders::_1);
x = 20;
f2(5); // 25 (reads the current x, 20)
Q5: Is bind deprecated?
A: No, but lambda is more recommended after C++11.
// bind: still valid, but...
auto f1 = std::bind(add, 5, std::placeholders::_1);
// lambda: more recommended
auto f2 = [](int x) { return add(5, x); };
Q6: What is a placeholder?
A: Indicates the position of the argument to be passed when calling.
using namespace std::placeholders;
// _1: first call argument
auto f1 = std::bind(add, 5, _1);
f1(10); // add(5, 10)
// _2: second call argument
auto f2 = std::bind(subtract, _2, _1);
f2(10, 3); // subtract(3, 10)
Q7: Is nested bind possible?
A: It’s possible, but it’s complicated. I recommend lambda.
// ❌ Nested bind: complicated
auto f = std::bind(add, std::bind(multiply, _1, 2), _2);
// ✅ Lambda: clear
auto f2 = [](int x, int y) { return add(multiply(x, 2), y); };
Q8: What are bind learning resources?
A:
- cppreference.com - std::bind
- “Effective Modern C++” by Scott Meyers (Item 34)
- “The C++ Standard Library” by Nicolai Josuttis
std::bind is a C++11 function that creates a new function object by prebinding a function and its arguments.
Related Articles
- C++ Initializer-List Constructors
- C++ Reference Collapsing: How T& && Becomes T&, and Why Forwarding References Depend on It
- How C++ auto Deduces Types: References, const, Braced Init and decltype(auto)