Function Binding and Partial Application

This lesson covers function binding and partial application using std::bind(), std::bind_front(), std::bind_back() and std::placeholders.
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
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

Earlier in the chapter, we introduced the concept of partial application. With partial application, we provide only some of a function’s required arguments - the remaining arguments are provided later, elsewhere in our code.

Being able to implement this approach gives us more flexibility in our designs, and is sometimes required. We introduced an example where we wanted to determine if a Player object was a minimum level but, because of constraints in our design, we were not able to provide the Player and MinimumLevel at the same time.

We solved this using a functor. We provided the MinLevel as a constructor argument, where it then got saved to a member variable. We then later provided the Player object as an argument to the () operator.

A simplified implementation of the full example we introduced in the Functors lesson is below:

#include <iostream>

struct Player {
  int GetLevel() const { return 45; }
};

struct LevelChecker {
  int MinLevel {50};
  bool operator()(const Player& P) {
    return P.GetLevel() >= MinLevel;
  }
};

int main() {
  Player PlayerOne;
  LevelChecker IsLevel40{40};
  LevelChecker IsLevel50{50};

  if (IsLevel40(PlayerOne)) {  
    std::cout << "Player is level 40 or above";
  }

  if (!IsLevel50(PlayerOne)) {
    std::cout << "\nBut not level 50 or above";
  }
}
Player is level 40 or above
But not level 50 or above

In this lesson, we’ll cover a more direct and flexible way of implementing partial application, using std::bind.

Binding and std::bind()

To support the partial application of a function, we can imagine generating a simple wrapper around that function. When creating the wrapper, we provide some (but not all) of the arguments that the function needs.

The wrapper can overload the () operator, thereby creating a function object (a functor) that we can call. When the functor is called, the caller provides the remaining arguments.

The functor then combines the arguments provided at its construction (the "bound" arguments) with the arguments provided to the () operator to invoke the underlying function.

Let's imagine we have the following function, that accepts three arguments:

#include <iostream>

void Func(int x) {
  std::cout << "x = " << x << '\n';
}

int main() {
  Func(1);
}
x = 1

We can use std::bind() from the <functional> header to create a functor wrapping this function:

#include <iostream>
#include <functional>

void Func(int x) {
  std::cout << "x = " << x << '\n';
}

int main() {
  auto Functor{std::bind(Func, 1)};
  Functor();
}
x = 1

We can bind multiple arguments by providing them when creating the functor:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { auto Functor{std::bind(Func, 1, 2, 3)}; Functor(); }
x = 1, y = 2, z = 3

The functor is also reusable - we can call it as many times as needed:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { auto Functor{std::bind(Func, 1, 2, 3)}; Functor(); Functor(); Functor(); }
x = 1, y = 2, z = 3
x = 1, y = 2, z = 3
x = 1, y = 2, z = 3

Partial Application

Binding all of a function’s arguments is sometimes useful, but more commonly we’ll only want to bind some. This allows other arguments to be provided when we later invoke our functor.

We’ll cover how to do this using std::bind() later in the lesson, but C++20 and C++23 introduced std::bind_front() and std::bind_back() respectively, which are easier to use and capture most use cases.

std::bind_front()

When we want to bind the leftmost arguments in a function’s parameter list and let the later arguments be provided separately, we can use std::bind_front().

Below, we bind the x value of Func() to 1, and let the y and z be provided later:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { auto Functor{std::bind_front(Func, 1)}; Functor(2, 3); Functor(5, 9); Functor(42, 97); }
x = 1, y = 2, z = 3
x = 1, y = 5, z = 9
x = 1, y = 42, z = 97

In this example, we bind both x and y, requring only z to be provided later:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { auto Functor{std::bind_front(Func, 1, 2)}; Functor(3); Functor(9); Functor(97); }
x = 1, y = 2, z = 3
x = 1, y = 2, z = 9
x = 1, y = 2, z = 97

std::bind_back()

Note*: std::bind_back() is a C++23 feature, and may not yet be available in all compilers. The behavior can be replicated using std::bind and std::placeholders in older specifications, which we cover below.*

The std::bind_back() function works in the opposite way to std::bind_front(). It lets us bind arguments at the end of our parameter list, leaving the leftmost arguments to be provided later. Below, we bind y to 2 and z to 3, with x being provided by the caller.

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { auto Functor{std::bind_back(Func, 2, 3)}; Functor(1); Functor(0); Functor(-5); }
x = 1, y = 2, z = 3
x = 0, y = 2, z = 3
x = -5, y = 2, z = 3

Using std::placeholders

Whilst the std::bind_front() and std::bind_back() functions can satisfy most use cases, there are more complex scenarios where we need a bit more flexibility. We can implement these using a combination of std::bind() and std::placeholders

Within our std::bind() argument list, we can use identifiers within the std::placeholders namespace to mark locations where future arguments will go. We have access to std::placeholders::_1, std::placeholders::_2, and so on.

When we later call our functor, the first argument we pass will be forwarded to where we placed std::placeholders::_1, the second will be forwarded to std::placeholders::_2, and so on.

Below, we reimplement our std::bind_front(Func, 1) example using these techniques. We bind the first argument of Func() to the integer 1, but leave placeholders for the remaining two parameters. We can then provide those when calling the functor:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { auto Functor{std::bind(Func, 1, std::placeholders::_1, std::placeholders::_2 )}; Functor(2, 3); Functor(5, 9); Functor(42, 97); }
x = 1, y = 2, z = 3
x = 1, y = 5, z = 9
x = 1, y = 42, z = 97

In this example, we introduce a using namespace declaration for std::placeholders. We also bind the second argument of our underlying function, allowing the first and third to be provided later:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { using namespace std::placeholders; auto Functor{std::bind(Func, _1, 100, _2)}; Functor(0, 200); Functor(200, 0); }
x = 0, y = 100, z = 200
x = 200, y = 100, z = 0

Reordering Arguments

Our placeholders do not have to be in ascending order. In this example, we don’t bind any arguments. Instead, we position our placeholders such that we create a functor that receives the same arguments as the underlying function, but in a different order:

#include <iostream>
#include <functional>

void Func(int x, int y, int z) {/*...*/} int main() { using namespace std::placeholders; auto Functor{std::bind(Func, _3, _2, _1)}; Functor(1, 2, 3); }
x = 3, y = 2, z = 1

Binding a Member Function

We can use std::bind() with member functions. When using this technique, we can treat the object that the member function will be called on as the first argument.

If the member function has any additional arguments, we would also provide those, either as bound values, or std::placeholder markers.

Below, we create a functor that, when invoked, behaves like a call to PlayerOne.GetName():

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne{"Roderick"}; auto GetPlayerName{ std::bind(&Player::GetName, &PlayerOne)}; std::cout << "Player: " << GetPlayerName(); }
Player: Roderick

The object the member function is called upon can also be a placeholder, allowing std::bind() to act as an alternative to std::mem_fn():

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne{"Roderick"}; auto GetPlayerName{std::bind(&Player::GetName, std::placeholders::_1)}; std::cout << "Player: " << GetPlayerName(&PlayerOne); }
Player: Roderick

Binding the Context Object

When binding a member function pointer, std::bind() allows us to provide the context object either as a value or a pointer, including a smart pointer.

Below, we pass a pointer to our context object. When the functor created by std::bind() is later invoked, it will visit that pointer to get the current state of our object:

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne{"Roderick"}; auto GetPlayerOneName{std::bind( &Player::GetName, &PlayerOne // Passing by pointer )}; std::cout << "Name: " << GetPlayerOneName(); PlayerOne.SetName("Anna"); std::cout << "\nName: " << GetPlayerOneName(); }
Name: Roderick
Name: Anna

We can alternatively bind by value, copying the context object into the functor created by std::bind(). When we later invoke the functor, it will use that copy, in the state it was when we bound it:

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne{"Roderick"}; auto GetPlayerOneName{std::bind( &Player::GetName, PlayerOne // Passing by value )}; std::cout << "Name: " << GetPlayerOneName(); PlayerOne.SetName("Anna"); std::cout << "\nName: " << GetPlayerOneName(); }
Name: Roderick
Name: Roderick

Using std::bind_front() and std::bind_back()

We can also use std::bind_front() or std::bind_back() with member functions where applicable.

The previous examples could be written using std::bind_front() or std::bind_back(), removing the need for the explicit placeholder:

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne{"Roderick"}; auto GetPlayerName{ std::bind_front(&Player::GetName)}; std::cout << "Player: " << GetPlayerName(&PlayerOne); }
Player: Roderick

In the following example, we use std::bind_front() to bind a member function that accepts an argument:

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne; auto SetPlayerOneName{std::bind_front( &Player::SetName, &PlayerOne)}; SetPlayerOneName("Anna"); std::cout << "Player: " << PlayerOne.GetName(); }
Player: Anna

Below, we modify the previous example to use std::bind_back(), thereby binding the member function’s Name parameter to "Anna", and accepting the Player we want to rename as an argument:

#include <iostream>
#include <functional>

struct Player {/*...*/}; int main() { Player PlayerOne; auto SetNameToAnna{std::bind_back( &Player::SetName, "Anna")}; SetNameToAnna(PlayerOne); std::cout << "Player: " << PlayerOne.GetName(); }
Player: Anna

The previous examples could be implemented using std::bind() and std::placeholders if preferred:

auto SetName{std::bind(&Player::SetName,
  &PlayerOne, std::placeholders::_1)};

auto SetNameToAnna{std::bind(&Player::SetName,
  std::placeholders::_1, "Anna")};

A More Complex Example

Let’s go back to our Party code to see a more complex example closer to real problems we’re likely to solve by binding. Let's imagine we want everyone in our Party to start a Quest, We already have a StartQuest() function that comes close to meeting our needs, but not quite:

#include <iostream>
#include <concepts>
#include <functional>

struct Player { std::string Name; };
struct Quest { std::string Name; };

class Party {/*...*/}; void StartQuest(Player& P, Quest& Q) { std::cout << P.Name << " has started " << Q.Name << '\n'; } int main() { Party MyParty; Quest EpicQuest{"The Epic Quest"}; MyParty.for_each(/* ??? */); }

Rather than writing a custom lambda or functor to integrate MyParty.for_each() and StartQuest(), we can instead bridge that gap using binding.

Below, we use std::bind_back() to create a functor that binds the EpicQuest parameter. The std::bind_back() function then returns a functor that accepts a single Player object as an argument, exactly what MyParty.for_each() requires.

Once it receives that argument, it forwards it to StartQuest(), and provides the EpicQuest we bound earlier as the second argument:

#include <iostream>
#include <concepts>
#include <functional>

struct Player { std::string Name; };
struct Quest { std::string Name; };

class Party {/*...*/};
void StartQuest(Player&, Quest&) {/*...*/} int main() { Party MyParty; Quest EpicQuest{"The Epic Quest"}; MyParty.for_each(std::bind_back( StartQuest, EpicQuest )); }
Anna has started The Epic Quest
Robert has started The Epic Quest
Roderick has started The Epic Quest

Prior to C++23, the previous example can be written using std::bind() and std::placeholders:

MyParty.for_each(std::bind(
  StartQuest, std::placeholders::_1, EpicQuest 
));

Summary

In this lesson, we explored the concepts of function binding and partial application and the flexibility they provide when working with functions. The main things we covered include:

  • A review of how user-defined functors can be used in partial application, illustrated with a Player and LevelChecker example.
  • An Introduction to binding and std::bind() - a generic wrapper for creating functors that wrap some underlying function.
  • Using std::bind_front() and std::bind_back() for easier binding of function arguments in C++20 and C++23.
  • An introduction to std::placeholders for marking positions of future arguments in std::bind() calls.
  • Various examples of binding member functions, thereby allowing them to be invoked as if they were free functions.
  • Walking through a more complex example demonstrating the use of function binding in a practical scenario involving a Party class and a Quest structure.

Was this lesson useful?

Next Lesson

Trailing Return Types

An alternative syntax for defining function templates, which allows the return type to be based on their parameter types
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Function Binding and Partial Application

This lesson covers function binding and partial application using std::bind(), std::bind_front(), std::bind_back() and std::placeholders.

A computer programmer
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
Working with Functions
A computer programmer
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:

  • 125 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 parameter types
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved