Algorithms and Function Arguments
Some algorithms require an operation to be called as a nonmember function, and this is the major use of mem_fn().
A Question About Empty Strings
Suppose you have a collection of strings and you want to count how many of them are empty. You already know how to ask one string whether it is empty: you call its member function on that string. The question this article answers is what to do when the work is not done by you directly, but by an algorithm that walks through the collection on your behalf. The algorithm has to perform the operation for every element, and the way it performs that operation is set by the rules of the algorithm, not by your own code.
What do you think happens?
If an algorithm is handed a list of strings and told to check each one, what kind of call would it need to make on each element?
Reveal answer
The algorithm needs the second form, empty(elem), in which the element is handed in as an argument.
An algorithm takes an element from the sequence and applies an operation to it. That is closer to writing operation(element) than to writing object.operation(). The mismatch between these two styles is what the rest of this article addresses.
Two Calling Styles Side by Side
A member function is attached to an object. You write the object first and then the operation, as in the expression object.operation(). This is the object-first style. An algorithm, however, does not usually know your object by name. It takes an element from the sequence and applies an operation to that element, which is the nonmember style: the element is supplied as an ordinary argument to the operation.
The source identifies the major use of mem_fn() as the case where an algorithm requires an operation to be called as a nonmember function. In other words, the algorithm is written to call its operation in the nonmember style, with the object supplied as an ordinary argument. A member function, which is written in the object-first style, does not fit that calling pattern on its own. This gap is what mem_fn() is meant to close.
Read the two columns as two different requests. The member style asks a specific object to perform its own operation. The nonmember style asks an operation to act on whatever object it is given. An algorithm that applies one operation to many elements works in the second way, so a member function written only in the first style cannot be handed to it directly.
What mem_fn() Does to a Member Function
Think of mem_fn() as a translator placed between the algorithm and the member function. The algorithm speaks in the nonmember style, passing each element as an argument. mem_fn() takes a member function and returns a callable object that the algorithm can invoke. When the algorithm invokes that object with an element, the element acts as the object, and the member call is performed on it.
Notice what does not change. The member function itself is not rewritten, and its behavior is the same as it would be if you called it directly. Only the way it is reached is reshaped so that the algorithm can use it. For that reason, mem_fn() changes how the function is reached, not what it does.
Counting Empty Strings with count_if
The standard library function that counts the elements of a range satisfying a condition can be given the member function empty() from the string class, wrapped with mem_fn(). The counting algorithm then applies that wrapped function to each string in the vector. The example below follows the pattern described in the source.
2- The vector holds four strings, two of which are empty.
- &std::string::empty is the member function, named by its address.
- std::
mem_fnwraps that member function into a callable object. count_ifcalls the callable object on each element and counts the true results.
Trace the run from left to right. The algorithm does not know about the vector's object-first calls. It hands each string to the wrapper, the wrapper makes the member call on that string, and the algorithm keeps a running count of the true results. The final count is 2. The member function was used exactly as it would be used directly; only its entry point was adapted.
Transforming Strings into Lengths
The same adaptor works with algorithms that produce new values rather than counts. Here the member function size() reports the length of each string, and a transforming algorithm collects those lengths into a second container. The only change from the previous example is the member function being adapted and the algorithm being used. The wrapping step is identical.
5 0 4 0- lengths is sized to hold one result per input string.
- std::transform applies the wrapped
size()call to each string. - Each result is written to the matching position in lengths.
Compare the two examples. In the counting case, the algorithm combined the results into one number. In the transforming case, it wrote one result per element into a second container. In both, mem_fn() did the same job: it let the algorithm treat a member function as a nonmember operation that receives each element.
Mistakes With mem_fn() Calls
Common mistake Passing the member function without taking its address
What it looks likestd::mem_fn(std::string::empty)Why it failsThe source says to take the addresses of member functions correctly. The member function has to be named by its address, not handed over as a bare name.
Do this insteadstd::mem_fn(&std::string::empty)Common mistake Expecting the wrapper to apply to elements of an unrelated type
What it looks likeWrapping std::string::empty and then running the algorithm over a vector of integers
Why it failsThe wrapper performs a member call on each element. The element has to be an object of the class that owns that member function, or the call has no meaning.
Do this insteadMake sure the sequence's element type matches the class of the member function, such as a vector of std::string for std::string::empty.
Common mistake Using mem_fn() on an ordinary nonmember function
Why it failsThe source limits
mem_fn()to member functions. Its purpose is to reshape a member call into the nonmember style that an algorithm expects.Do this insteadPass an ordinary function directly to the algorithm instead of wrapping it.
Common mistake Believing mem_fn() changes what the member function does
Why it failsThe source is explicit that it changes how the function is reached, not what it does. The result of
empty()is the same whether you call it directly or through the wrapper.Do this insteadReason about the member function's behavior as you normally would, and treat the wrapper only as a change in calling style.
Predicting a Transformed Result
Given the vector words containing the strings "cat", "", "lion", and "ox", what sequence of numbers would std::transform produce if it applied std::mem_fn(&std::string::size) to each element and wrote the results into a vector of size_t? Work through each element before checking.
Hints
- Apply the
size()member call to each string in order: cat, then the empty string, then lion, then ox. - The empty string has length 0, and the other three lengths are the number of characters each contains.
Key Takeaways
- Some algorithms call their operation in the nonmember style, passing each element as an argument, while a member function is normally called on an object in the object-first style.
mem_fn()takes a member function and returns a callable object; the algorithm invokes that object with an element, and the element acts as the object for the member call.mem_fn()is used with member functions, and the member function must be named by its address, as in &std::string::empty.mem_fn()changes how a member function is reached, not what it does, so the function's behavior is unchanged.