4 Creating new functions from the old ones
This chapter explores practical techniques for generating new functions from existing ones to improve reuse, clarity, and adaptability in C++. It shows how thinking in terms of small, composable transformations enables you to fit functions to algorithm requirements, build pipelines of operations, and scale simple ideas to broader contexts. The core themes are partial function application, currying (viewing multiargument functions as chains of unary functions), function composition, and lifting functions to operate over richer structures.
The discussion begins with partial function application: fixing some arguments of a function to create a new, more specialized callable. It progresses from hand-rolled functors to a generic “bind second” wrapper, then to std::bind and its placeholders for selective binding and argument reordering, including binding member functions (with std::ref for references) and adapting callables for standard algorithms. The chapter contrasts std::bind’s terse flexibility with lambdas’ readability and optimizer-friendliness, showing equivalent lambda formulations for the same adaptations. Concrete examples include creating predicates like “greater than 42,” converting degrees to radians by partially applying multiplication, sorting via reversed comparator arguments, and tailoring printing functions for people by pre-binding stream and format.
Currying reframes any multiargument function as a sequence of unary functions, enabling you to supply arguments incrementally and even write generic helpers such as make_curried. A database case study demonstrates how curried APIs reduce overload explosions and make it trivial to specialize on the first argument without knowing the remaining arity—something that becomes cumbersome with std::bind. The chapter then applies function composition to build a clear pipeline for the “most frequent words” problem by chaining small transformations. Finally, it revisits function lifting: starting from a function on a single value (like uppercasing a string), you systematically “lift” it to pointers, containers, and maps, and generalize patterns such as reversing pairs across collections—illustrating how functional and object-oriented styles reinforce each other through reusable, composable building blocks.
On a higher level, you have a function of two arguments: one object, and an integer value representing the age limit. The function returns whether the object is older than that age limit. When you fix the age limit to a specific value, you get a unary function that compares the object’s age against that predefined value.
Fixing the second argument of the greater-than comparison operator to 42 turns it into a unary predicate that tests whether a value is greater than 42.
Binding one argument of multiplication to π/180 gives you a function that converts degrees to radians.
The two-argument function tests whether its first argument is greater than its second. Binding both arguments to specific values such as 6 and 42 creates a nullary function object that, when called, returns the result of greater(6, 42).
The two-argument function tests whether its first argument is greater than its second. Binding one of the arguments to a value and the other to a placeholder creates a unary function object that, when called with a single argument, uses that argument to fill the hole defined by the placeholder.
You specify the first placeholder to be the second argument passed to greater, and vice versa. This results in a new function that’s the same as greater, but with the arguments switched.
You want to create a function that prints names of people to the standard output. You have a function that can write a person’s information in multiple formats and to different output streams. You specify the output stream and the format while leaving the person empty, to be defined by the std::for_each algorithm.
If you want to use lambdas instead of std::bind, you need to convert bound arguments to captured values; the unbound arguments will become lambda arguments.
The function takes two arguments, and you’re converting it to a unary function that returns not a value, but a new unary function. This allows you to pass in the arguments one at a time rather than all at once.
You started with a function of three arguments and converted it to its curried version. The curried version is a function that accepts a person and returns a function that takes an output stream. That function, again, returns a new function that takes the output format and prints the person’s info.
Different applications have different needs; some may want to connect to multiple databases, some may need multiple sessions, and some need just a single session while connected to a single database.
You’re transforming the original text to a list of words, assigning a count to each word, and then sorting the words based on the number of times they appear in the text.
You’re lifting a function that transforms a single string to a few functions that transform strings contained in various container-like types.
You have a collection of pairs that you want to reverse and a function that’s capable of reversing a single pair. By lifting the function, you get a function that can reverse each pair in a collection.
Summary
- Higher-order functions such as
std::bindcan be used to transform existing functions into new ones. You can get a function of n arguments and easily turn it into a unary or a binary function that you can pass to algorithms such asstd::partitionorstd::sortwithstd::bind. - Placeholders give you a high level of expressiveness when defining which function arguments shouldn’t be bound. They allow you to reorder the arguments in the original function, to pass the same argument multiple times, and so forth.
- Although
std::bindprovides a terse syntax to do partial function application, it might have performance penalties. You may want to consider using lambdas when writing performance-critical code that’s often invoked. They’re more verbose, but the compiler will be able to optimize them better than the function objects created bystd::bind. - Designing an API for your library is hard. Many use cases need to be covered. Consider creating an API that uses curried functions.
- One of the often-heard statements about functional programming is that it lets you write shorter code. If you create functions that are easily composed, you can solve complex problems with a fraction of the code the usual imperative programming approach would take.
Functional Programming in C++ ebook for free