Concepts / Function Adaptors and Objects

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.

  • Programming

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?

  • A call on the object itself, written object first

  • A call that takes each element as an ordinary argument

  • A call that copies the whole collection before checking

Reveal answer
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.

objectoperationobject.operation()operation(element)object comes firstelement passed as argument
How the object and the operation are arranged in a member call compared with a nonmember call that receives the element as an argument.

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.

algorithmmem_fn objectelementmember functioncalls with elementtreats element as the objectperforms member call on that element
The algorithm hands an element to the mem_fn object, which performs the member call on that same element.

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

Definition

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.

Key point

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.

first stringsecond stringthird stringcount of matchesnext elementnext elementtally results
The algorithm visits each string in turn, applies the adapted empty() check to it, and accumulates the results into a count.
Worked example

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?

  1. 1
    Wrap the member function

    mem_fn applied to empty() produces a callable object. The algorithm can call it with each string as the element.

  2. 2
    Visit the first element

    The first string is empty, so the adapted empty() check returns true and this element is counted.

  3. 3
    Visit the second element

    The non-empty word makes the check return false, so it is not counted.

  4. 4
    Visit the third element

    The second empty string makes the check return true, so it is counted.

  5. 5
    Report the count

    Two elements satisfied the condition, so the algorithm returns a count of two.

Result

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 fails

    The 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 instead

    Apply 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 fails

    The 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 instead

    Supply 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 fails

    The 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 instead

    Reason 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

MEDIUM

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.
HARD

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 collecting size() results into a second container.

More concepts starting with F