Function Adaptors and Objects
mem_fn() changes how the member function is reached, not what it does. The operation still does exactly what the member function does; the adaptor only supplies the calling style the algorithm expects.
Counting Empty Strings in a Collection
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.
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
A call that takes each element as an ordinary argument
The algorithm does not usually know your object by name. It takes an element from the sequence and applies an operation to that element, which is closer to writing operation(element) than object.operation().
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. This is the starting point for everything that follows: the algorithm's calling style determines what kind of function it can use.
Member Calls Versus Nonmember Calls
A member function is attached to an object. You write the object first and then the operation, as in the expression object.operation(). The object is the thing the operation belongs to, and the call is written in that object-first style.
An algorithm is written to call its operation in the nonmember style, with the object supplied as an ordinary argument. A member function, written in the object-first style, does not fit that calling pattern on its own. This mismatch is the gap that mem_fn() is meant to close. The source identifies this case as the major use of mem_fn(): an algorithm that requires an operation to be called as a nonmember function.
Tracing What the Algorithm Passes Along
Before looking at the adaptor itself, trace the path an element takes when an algorithm applies a member function to it. The algorithm takes one element from the sequence. It does not perform a member call itself. It hands the element to the operation it was given, and that operation is expected to accept the element as an argument.
Read the diagram from top to bottom. The element is the same object throughout. The adaptor does not copy it into a new object or change which object the operation acts on; it only receives the element from the algorithm and makes the member call on it.
How mem_fn() Closes the Gap
mem_fn() takes a member function and returns a callable object that the algorithm can invoke, with each element acting as the object.
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. The translator receives that element and performs the member call on it. The member function itself does not change. Only the way it is reached is reshaped so the algorithm can use it.
mem_fn() changes how the member function is reached, not what it does. The operation still does exactly what the member function does; the adaptor only supplies the calling style the algorithm expects.
Walking Through a Sequence of Objects
The source's first example uses a standard library function that counts the elements of a range satisfying a condition, together with the member function empty() from the string class. The member function is wrapped with mem_fn() so that the counting algorithm can apply it to each string in a vector.
Counting empty strings with a wrapped empty() check
A sequence holds three strings: an empty string, a non-empty word, and another empty string. Using an algorithm that counts elements satisfying a condition, with the condition given as mem_fn applied to empty(), how many elements are counted?
- 1Wrap the member function
mem_fnapplied toempty()produces a callable object. The algorithm can call it with each string as the element. - 2Visit the first element
The first string is empty, so the adapted
empty()check returns true and this element is counted. - 3Visit the second element
The non-empty word makes the check return false, so it is not counted.
- 4Visit the third element
The second empty string makes the check return true, so it is counted.
- 5Report the count
Two elements satisfied the condition, so the algorithm returns a count of two.
The count is 2.
Transforming Instead of Counting
The same adaptor works with algorithms that produce new values rather than counts. In the source's second example, the member function size() reports the length of each string, and a transforming algorithm collects those lengths into a second container.
Notice that the only change from the counting example is the member function being adapted and the algorithm being used. The mechanism is the same: the algorithm passes each element as an argument, mem_fn() performs the member call on that element, and the result is collected. You do not need a new idea to handle a new algorithm, only a different member function and a different algorithm.
Mistakes When Passing Member Functions Through mem_fn()
Common mistake Using mem_fn() with a function that is not a member function
Why it failsThe source states that
mem_fn()is used with member functions only. Its purpose is to reshape the call to a member function so that an element can act as the object.Do this insteadApply mem_fn() only to member functions. For an ordinary nonmember function, pass the function directly to the algorithm.
Common mistake Passing the member function without taking its address correctly
Why it failsThe source specifically cautions that the member function must be taken correctly. A member function handed over in the wrong form will not be reached as the adaptor expects.
Do this insteadSupply the member function to mem_fn() in the correct form, taking its address as the source describes, and then check that the adapted object is what the algorithm receives.
Common mistake Expecting mem_fn() to change what the member function computes
Why it failsThe source says
mem_fn()changes how the function is reached, not what it does. Believing otherwise leads to confusion when the result matches the member function's ordinary behavior.Do this insteadReason about the adapted call exactly as you would reason about the member call itself. The adaptor only changes the calling style.
Common mistake Forgetting that the algorithm, not your code, performs the loop over the elements
Practice Applying the Adaptor
An algorithm counts the elements of a sequence of strings for which a condition holds. You want to count the strings whose length is zero, using the member function empty(). Describe how mem_fn() fits into this setup: what the algorithm passes in, what the adaptor does with it, and what the algorithm finally reports.
Hints
- Start from the algorithm's calling style: it passes each element as an ordinary argument.
- The adaptor receives that element and performs the member call on it.
- The final answer is the number of elements for which the adapted check returned true.
Take the transforming example from earlier and describe what would change, and what would stay the same, if you wanted the algorithm to collect the lengths of strings after dropping any strings that are empty. Explain which part of the setup you would modify and why the calling mechanism would not need to change.
Hints
- Think about which piece changes: the member function or the algorithm or both.
- The mechanism of passing each element through
mem_fn()stays the same.
Key Takeaways
- Some algorithms are written to call their operation as a nonmember function, while a member function is normally called on an object in object-first style; this mismatch is the major use of
mem_fn(). mem_fn()takes a member function and returns a callable object, so each element the algorithm passes in acts as the object for the member call.mem_fn()changes how the member function is reached, not what it does, and it should be used with member functions only.- The same adaptor pattern applies to counting algorithms such as checking
empty(), and to transforming algorithms such as collectingsize()results into a second container.