Partial function application is the process of taking a function with a number of arguments, fixing (or binding) some of its arguments and producing a new function with a smaller number of arguments. In C++, this can be achieved with std::bind() that generates a forwarding call wrapper for a callable object, and the placeholder objects from the std::placeholders namespace that are used for binding arguments to the callable object.
To see how this works, let’s start with the following function that is supposed to print a document to a specified printer with various settings. These include page orientation and size, number of copies, size of margins, resolution, and scale. Some of these have default values because in most cases you’d use the same values for these arguments.
enum class page_size {Letter, A0, A1, A2, A3, A4, A5, A6}; enum class page_layout {portrait, landscape}; enum class page_margins {standard, minimum, maximum}; void print(std::string_view destination, page_layout const orientation, page_size const size, int const copies = 1, page_margins const margins = page_margins::standard, int const dpi = 300, double const scale = 1.0) { /* print document */ }
If you want to print to a printer called “Printer” one copy on an A4 page in portrait layout, with standard margins, 300 DPI and no scaling, then you make the following call, relying on the default values.
print("Printer", page_layout::portrait, page_size::A4);
Should you need two copies of the document, you need to explicitly provide that argument in the call.
print("Printer", page_layout::portrait, page_size::A4, 2);
What if you need to print with a different resolution, such as 600 DPI instead of 300? In C++, arguments are positional, they need to be supplied in the order defined by the function declaration. Other languages, such as C#, support named arguments so that you can provide arguments in any order given that you specify the name of the parameter. Because of this, however, when you need to specify other value than the default for the DPI, you also need to specify values for the number of copies and the margins. In other words, your call would look like this.
print("Printer", page_layout::portrait, page_size::A4, 1, page_margins::standard, 600);
This is where std::bind() can step in. Default arguments for function parameters and binding are different things, but the effects are somehow similar. For instance, if all we need to change is the page size and margins, we can leverage a partial function application on the original function (that may also have default arguments) and create a callable forwarding wrapper with placeholders for the parameters we need to supply on every call and fix values for the others. Placeholders are objects in the std::placeholders namespace called _1, _2, …, _N (where N is implementation defined), whose type is also an implementation detail (although it must be default and copy-constructible).
using namespace std::placeholders; auto fprint = std::bind(print, "Printer", page_layout::portrait, _1, 1, _2, 300, 1.0); fprint(page_size::Letter, page_margins::maximum); fprint(page_size::A3, page_margins::minimum);
In this example _1 and _2 are objects that are stored in the function object generatated by std::bind(). When the function object is invoked with arguments, each placeholder is replaced by the corresponding unbound argument (_1 by the first argument, _2 by the second, etc.). One thing to notice is that when creating a callable wrapper with std::bind() you must supply values even for the parameters with default arguments.
Of course, you’re not going to create a binding for making one or two calls like that. But you may have situations when one function with multiple arguments could be called repeatedly within the same routine or algorithm with only some of them changed. In this case you can use std::bind().
It is also useful when you want to use a function with multiple arguments with a standard algorithm that expects fewer arguments than the function requires. In the following example, the print() function is used to print the same document to multiple printers. std::for_each is used for this purpose, although the only argument that is supplied to the function is the printer name. Therefore, a callable wrapper is created with std::bind().
std::vector<std::string> printers{ "Printer", "Default printer", "PDF printer" }; std::for_each( std::begin(printers), std::end(printers), std::bind(print, _1, page_layout::portrait, page_size::A4, 1, page_margins::standard, 600, 1.0));
In the examples so far, the print() function was a free function, but the process works the same with member functions too. In the following example, print() is a member of the document class.
struct document { void print(std::string_view destination, page_layout const orientation, page_size const size, int const copies = 1, page_margins const margins = page_margins::standard, int const dpi = 300, double const scale = 1.0) { /* print document */ } };
To invoke this member function on a particular instance of the class you must suply the object as an argument to std::bind(); the rest is the same.
document doc; auto fprint = std::bind(&document::print, &doc, "Printer", page_layout::portrait, _1, 1, _2, 300, 1.0); fprint(page_size::Letter, page_margins::maximum); fprint(page_size::A3, page_margins::minimum);
std::for_each( std::begin(printers), std::end(printers), std::bind(&document::print, &doc, _1, page_layout::portrait, page_size::A4, 1, page_margins::standard, 600, 1.0));