Concepts / Algorithms and Function Arguments

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().

  • Programming

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?

  • elem.empty(), with the object written first and the member function after a dot

  • empty(elem), with the element passed in as an ordinary argument

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

elem.empty()object first, operationsecondf(elem)element passed as anargument
The member call puts the object first and the operation after it. The nonmember call, which algorithms expect, passes the element as an argument to the operation.

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.

&std::string::emptythe member function, givenby its addressstd::mem_fnwrapper that takes themember functioncallable objectwhat the algorithm receiveselem.empty()member call made by thewrapperbool resulttrue or falsewords elementa string from the sequencepassed inreturnsinvokedelement as the objectproduces
The member function's address goes into mem_fn(), which returns a callable object. The algorithm later passes an element to that object, and the element becomes the object the member call runs on.

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.

cpp
Output
2
Notes on the code
  • The vector holds four strings, two of which are empty.
  • &std::string::empty is the member function, named by its address.
  • std::mem_fn wraps that member function into a callable object.
  • count_if calls the callable object on each element and counts the true results.
words[0]apple: falsewords[1]empty string: truewords[2]pear: falsewords[3]empty string: truecount2next elementnext elementnext elementtotal true results
As count_if walks the vector, each string is handed to the wrapped empty() call in turn, and the result for that string is recorded before moving on.

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.

cpp
Output
5 0 4 0
Notes on the code
  • 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 like
    std::mem_fn(std::string::empty)
    Why it fails

    The 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 instead
    std::mem_fn(&std::string::empty)
  • Common mistake

    Expecting the wrapper to apply to elements of an unrelated type

    What it looks like

    Wrapping std::string::empty and then running the algorithm over a vector of integers

    Why it fails

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

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

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

    Pass an ordinary function directly to the algorithm instead of wrapping it.

  • Common mistake

    Believing mem_fn() changes what the member function does

    Why it fails

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

    Reason 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

EASY

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.

Related concepts

More concepts starting with A