There is one more technique that we’re likely to find useful, and that is the ability to manipulate and bind the arguments of functions that already exist.
std::bind
Binding is a useful technique to deal with scenarios where you already have a function that almost meets your needs, but the arguments are not quite right.
This standard library provides two useful helpers for this: std::bind
and std::placeholders
are both available within <functional>
Let’s take a look at bind
in isolation first. For example, let's imagine we already have a function that returns a boolean if a given Character
is alive:
#include <functional>
using std::bind;
class Character {
public:
bool isAlive() const {
return true;
}
};
Character PlayerCharacter;
bool isAlive(const Character& Character) {
return Character.isAlive();
}
We now need to create a function that returns true
if the character the current player is controlling is alive. We could create a new function in the normal way:
bool isPlayerCharacterAlive() {
return isAlive(PlayerCharacter);
}
But, we could also just use the existing function if we bind its argument to the value we want. We can do that using std::bind
which will return a new callable, which we can invoke as needed:
auto isPlayerCharacterDead {
bind(isAlive, PlayerCharacter)
};
isPlayerCharacterDead();
As an extension of this idea, we’ll also often want to only bind some of the arguments to a function. This is called partial application.
Previously, we’ve always provided a function with all the arguments it needs upfront.
Partial application is the idea that we don’t necessarily need to do that. For example, imagine we have a function that accepts two arguments.
Rather than providing both arguments, we provide only one. This action returns another callable, which we might call a “partially applied function”. We can then later call that function, with the remaining arguments.
If we do not provide all the function arguments when using std::bind
, we get this behavior by default:
#include <iostream>
#include <functional>
using std::cout, std::endl;
int Add(int x, int y) {
return x + y;
}
int main() {
// We provide the first argument - 5 - here
auto Add5 { bind(Add, 5) };
// And the second argument - 1 - here
cout << Add5(1) << endl;
// Add5 is a "partially applied function"
// We can keep using it:
cout << Add5(5) << endl;
cout << Add5(20) << endl;
}
This program logs out:
6
10
25
In the first line of our main
function, we are creating a new callable called Add5
. When invoked, this function will call our Add
function with two arguments. The first argument will be 5
, and the second argument will come from the first argument on each invocation of Add5
Another name that is commonly used for partial application is “currying”.
Partial application is useful when we are going to call a function many times throughout our code base, but one or more of the arguments is going to be the same in most, or every invocation.
This is particularly problematic if determining what that argument should be has a large performance cost.
Rather than repeatedly calculating and providing that same argument, we can provide it just once to create a partially applied function. We can then call the partially applied function as many times as needed.
std::placeholders
Behind the scenes, std::bind
is leaving placeholders for future arguments, using std::placeholders
. For simple cases, the default behavior is exactly what we want, but we can also make this explicit:
auto Add5 { bind(Add, 5, placeholders::_1) };
We also have access to placeholders::_2
, placeholders::_3
, and so on. The benefit of using std::placeholders
directly is it lets us define the mapping between the arguments of the partially applied function, and the arguments of the bound function.
For example, if we wanted to provide the 1st and 3rd arguments to a function, but leave the 2nd as a placeholder, we’d need to use std::placeholders
using std::string, std::cout, std::bind, std::placeholders;
void LogStrings(
string Start, string Middle, string End
) {
cout << Start << Middle << End;
}
auto SayHello {
bind(
LogStrings,
"Hello ",
placeholders::_1, ",
Welcome Back!"
);
}
SayHello("Ryan");
Hello Ryan, Welcome Back!
Let’s go back to our Party.every
code to see a more complex example that is closer to real problems we’re likely to solve by partial application. Let's imagine we want to check if every character in our party is ready to start an epic quest:
int main() {
Quest EpicQuest;
Party Fellowship;
bool canStartQuest {
Fellowship.every(/* TODO: Pass a function here */);
}
}
We may already have a function that checks if a specific Character
is ready to start a specific Quest
:
bool canStartQuest(
const Character& Character, const Quest& Quest
) {
// ...
}
This existing function almost meets our needs, the only problem is that our Party.every
expects to receive a function that accepts a const Character&
as its only argument, whereas our current function has a second const Quest&
 argument.
Rather than creating a new function, let's use bind
and placeholders
to solve the problem:
int main() {
Quest EpicQuest;
Party Fellowship;
auto Predicate {
bind(canStartQuest, placeholders::_1, EpicQuest)
};
bool canStartQuest {
Fellowship.every(Predicate)
};
}
So, Predicate
is now exactly in the form that Party::every
wants - a function that accepts a single const Character&
argument and returns a bool
.
Every time Predicate
is called, canStartQuest
is going to be called with two arguments. The first argument will be the first argument passed to Predicate
, for which we have left a placeholder for using placeholder::_1
. The second argument will always be the same - a const reference to EpicQuest
.
Partial application, and this style of programming in general, can be very confusing. It’s normal to be struggling at this point. Familiarity with these concepts, and recognizing where they can be used, just comes with practice and experience.
As a final example, let's see a slightly simpler, but more niche use of std::placeholders
:
std::placeholders
Another option available to us using std::placeholders
is the ability to change the order of arguments. For example, we can put placeholders::_2
before placeholders::_1
:
#include <iostream>
#include <functional>
using std::cout, std::bind, std::placeholders;
int Subtract(int x, int y) {
return x - y;
}
int main() {
cout << Subtract(5, 2) << "\n";
auto InvertedSubtract {
bind(Subtract, placeholders::_2, placeholders::_1)
};
cout << InvertedSubtract(5, 2) << "\n";
}
This program logs out the following:
3
-3
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.