Lesson 4
std::bind and Its Alternatives
Lesson Introduction

Welcome! In modern C++ development, crafting flexible and reusable code is key to building highly maintainable applications. One powerful tool to aid in this endeavor is std::bind, which allows you to create function objects by binding specific arguments to functions. By the end of this lesson, you will understand std::bind, learn its syntax, explore its usage, and become familiar with lambda expressions as an alternative.

Introduction to std::bind

std::bind is part of the <functional> library in C++. It allows you to bind one or more arguments to a function, creating new callable objects. Callable objects are entities that can be called as if they are functions, including normal functions, function objects, and lambda expressions. Binding defines the values for the arguments, but it doesn’t invoke the function. The function is invoked only when someone calls the function object returned by std::bind. We have created such functions manually in the previous lesson to practice; std::bind can help you achieve the same result faster and easier!

Example Using std::bind

Here's an example to understand std::bind:

C++
1#include <iostream> 2#include <functional> 3 4int add(int a, int b) { 5 return a + b; 6} 7 8int main() { 9 // Using std::bind to create a new function that always adds 5 10 auto add_five = std::bind(add, std::placeholders::_1, 5); 11 std::cout << "3 + 5 = " << add_five(3) << '\n'; // Output: 3 + 5 = 8 12 13 return 0; 14}

In this example:

  • add is a function that takes two ints and returns their sum.
  • std::bind creates add_five by binding the second argument of add to 5.
  • Calling add_five(3) results in add(3, 5), producing 8.

Placeholders are special objects used in std::bind to represent arguments provided later. By using a placeholder, we say: "Hey, there will be an argument, but it is not present right now!"

In this example, std::placeholders::_1 means that the first parameter to add_five will become the first parameter to add.

If you had a need for additional parameters, you could use std::placeholders::_2 for the second parameter, std::placeholders::_3 for the third, and so on.

Binding by reference

Binding by reference can be useful when you need the bound parameter to reflect any changes made to the original variable. This means that the bound function will use the current value of the variable when invoked, not the value it had when the function was created.

To bind by reference, use std::ref for non-const references and std::cref for const references:

C++
1#include <iostream> 2#include <functional> 3 4int add(int a, int b) { 5 return a + b; 6} 7 8int main() { 9 int x = 5; 10 auto add_ref = std::bind(add, std::ref(x), std::placeholders::_1); 11 std::cout << "5 + 3 = " << add_ref(3) << '\n'; // Output: 5 + 3 = 8 12 13 x = 10; 14 std::cout << "10 + 2 = " << add_ref(2) << '\n'; // Output: 10 + 2 = 12 15 16 const int y = 10; 17 auto add_const_ref = std::bind(add, std::cref(y), std::placeholders::_1); 18 std::cout << "10 + 2 = " << add_const_ref(2) << '\n'; // Output: 10 + 2 = 12 19 20 return 0; 21}

In this example:

  • std::ref(x) binds x by reference which means x retains its original memory address. Therefore, changes to x will be reflected in the bound function. Initially, x is 5, so add_ref(3) results in add(5, 3). When x is changed to 10, add_ref(2) results in add(10, 2).

  • std::cref(y) binds y by const reference. This means the bound function can only read, not modify, y. The example binds y, which is 10, and add_const_ref(2) results in add(10, 2). Note that attempts to modify y would result in compilation errors due to its const qualification.

Binding by reference is particularly useful for working with large data structures where copying them would be inefficient.

Alternatives to std::bind: Lambda Expressions

C++11 introduced lambda expressions, offering a more concise and flexible alternative to std::bind. Lambdas allow you to create anonymous functions inline, often resulting in clearer, easier-to-maintain code. Although std::bind provides a nice, terse syntax for creating function objects that bind or reorder arguments of existing functions, it comes with a cost: it makes the job of the compiler much more difficult, and it’s harder to optimize. Whereas lambdas are a core-language feature, which means the compiler can optimize them more easily.

Recreate the add_five function object using a lambda expression:

C++
1#include <iostream> 2 3int add(int a, int b) { 4 return a + b; 5} 6 7int main() { 8 // Using a lambda expression to create a new function that always adds 5 9 auto add_five_lambda = [](int a) { return add(a, 5); }; 10 std::cout << "5 + 3 using lambda = " << add_five_lambda(3) << '\n'; // Output: 5 + 3 using lambda = 8 11 12 return 0; 13}

The lambda [ ](int a) { return add(a, 5); } creates an anonymous function that takes one integer and adds 5.

Comparison: std::bind vs Lambda Expressions

Let's compare std::bind and lambda expressions:

  • Readability

    • std::bind: Can be verbose and harder to read with complex bindings.
    • Lambda Expressions: Generally more readable and concise, easier to understand.
  • Ease of Use

    • std::bind: Useful for binding arguments without creating new function objects.
    • Lambda Expressions: Provide greater flexibility and are easier to write inline.
  • Performance

    • Lambda Expressions: Usually offer better optimizations because they are a core-language feature, making it easier for the compiler to optimize them.
Why Create Unary Functions in Functional Programming?

In functional programming, creating unary functions (functions that take a single argument) is a common practice for several reasons:

  • Simplification: Unary functions simplify complex logic by breaking it down into smaller, more manageable pieces. Each function performs one specific task, making it easier to understand and maintain.

  • Composition: Unary functions can be easily composed to create new functions. This means you can combine simple functions to form more complex operations without rewriting existing code.

  • Partial Application: Techniques like std::bind allow partial application, where some arguments of a multi-argument function are fixed, creating a new function that takes fewer arguments. This can make function reuse more efficient and expressive.

  • Higher-Order Functions: Functional programming often involves higher-order functions—functions that take other functions as arguments, return them, or both. Unary functions are easier to pass around as arguments or return values, facilitating powerful and flexible code patterns.

Lesson Summary

Today, we've explored how std::bind creates bound function objects by fixing certain arguments. We also introduced lambda expressions as a concise and flexible alternative.

Next, you'll get hands-on practice with std::bind and lambda expressions to consolidate your understanding and improve your coding skills. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.