C++ Binding and Currying

Learn to create functions from existing functions, by providing only some of the arguments.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

5c.jpg
Ryan McCombe
Ryan McCombe
Posted

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.

Binding and 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.

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

Currying

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!

A More Complex Example

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:

Changing Argument Order with 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

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Trailing Return Types

An alternative syntax for defining function templates, which allows the return type to be based on their argument types
3D Concept Art
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved