Jonathan Boccara's blog

How to Pass Class Member Functions to STL Algorithms

Published March 6, 2020 - 0 Comments

Daily C++

The C++ standard library makes it easy to use free functions with its STL algorithms. For example, with std::transform, we can write code like this:

auto const inputs = std::vector<int>{1, 2, 3, 4, 5};
auto const results = std::vector<int>{};

std::transform(begin(inputs), end(inputs), back_inserter(results), myFunction);

This has the effect of calling myFunction on each element of inputs and putting the results of those function calls in the results vector.

But if the elements of the inputs vector are classes with member functions:

class X
{
public:
    explicit X(int value) : value(value) {}
    int getValue() const { return value; }
private:
    int value;
};
auto const inputs = std::vector<X>{ X(1), X(42), X(3) };

Then we can’t pass member function to the STL algorithm:

auto const inputs = std::vector<X>{ X(1), X(42), X(3) };
std::vector<int> results;

std::transform(begin(inputs), end(inputs), back_inserter(results), X::getValue); // doesn't compile!

There are several ways that I’ve seen used in code to circumvent this limitation, and some are better than others.

Using a lambda: a sub-optimal solution

One simple way to end up calling the member function on the elements of the collection is to wrap it in a lambda:

std::transform(begin(inputs), end(inputs), back_inserter(results), [](X const& input){ return input.getValue(); });

While this is conceptually simple and does the right thing, this is a sub-optimal solution.

Indeed, the syntax of the lambda adds noise to the code and needlessly introduces a new object, input, that is at a lower level of abstraction than the surrounding code working at the level of the whole collection).

Note that using std::bind is in the same spirit but with even more noise and has all the disadvantages of using std::bind over using lambdas explained in item 34 of Effective Modern C++.

std::function: a costly solution

Instead of rolling out a lambda, we can think of using the function objects provided by the standard library. The most famous one is probably std::function, that appeared in C++11:

std::transform(begin(inputs), end(inputs), back_inserter(results), std::function<int(X const&)>(&X::getValue));

It’s not an optimal solution either. To understand why, here is a brief recap of how std::function works.

std::function accepts pretty much anything that is callable (free functions, member functions, function objects) and wraps it in an object defining an operator() that forwards the call to the wrapped callable thing.

In the general case, the template type of std::function is the type of the wrapped function. In the case of a member function it is a little different: it is essentially the type of what would have been that member function if it were taken out of the class and turned into a free function. So here it would be a function taking a const object of type X (indeed, getValue is a const member function of X) and returning an int, hence the <int(X const&)> template type.

But using std::function here is like using a steamroller to swat an ant. Hmm. Maybe this is too extreme a comparison. Let’s not get carried away, let’s just say using a hammer to swat an ant. That sounds more reasonable.

Either way, the point is that std::function is too powerful (and as a result, needlessly complex and costly) for the usage we’re making of it. The power of std::function is that it represents a value that can wrap various types of callable entities (free function, member function, function object) in the same type.

This allows to store such std::functions in a vector for example, which we don’t need here. And to achieve this, there is a delicate mechanism involving runtime polymorphism and indirections, that has some cost.

Thanks to Stephan T. Lavavej for his 2015 CppCon talk, where I learned this aspect of std::function.

std::mem_fn

Here is now the most adapted tool for passing member functions to STL algorithms: std::mem_fn, from the <functional> header:

std::transform(begin(inputs), end(inputs), back_inserter(results), std::mem_fn(&X::getValue));

std::mem_fn appeared in C++11 and wraps a class member function and defines an operator() that accepts an object of that class and invokes the method on the object. Just what we need.

Note that we have to pass a pointer to member function, that is &X::getValue, and not just X::getValue. It was also the case with std::function. This is so because there is no such thing as a reference to member function in C++. There are references (and pointers) to free functions, pointers to member functions, but not references to member functions. I couldn’t find why, and if someone knows, please leave a comment to let me know!

If you’ve heard of std::mem_fun_ref, be careful not to mix up std::mem_fn and std::mem_fun_ref.

std::mem_fun_ref is an older attempt in C++98 to achieve what std::mem_fn is doing. But std::mem_fn is superior and std::mem_fun is deprecated in C++11 and removed in C++17. So any occurrence of std::mem_fun_ref in your codebase will prevent you from upgrading to C++17. Luckily, they are easy to fix: just replace std::mem_fun_ref by std::mem_fn.

If you’re curious about why std::mem_fn is better than std::mem_fun_ref if they seem to do the same thing, here are two reasons:

  • the operator() in the function object generated by std::mem_fun_ref accepts only one parameter (the object of the class) whereas the one generated by std::mem_fn also accepts additional parameters that it forwards to the class method. std::mem_fn therefore allows to use class methods with arguments, whereas std::mem_fun_ref doesn’t.
  • the name “mem_fun_ref” is even weirder than “mem_fn“. Perhaps mem_fn should have been named mem_fun for member function, but I guess it wasn’t because this name was already taken by std::mem_fun, a sibling of std::mem_fun_ref that also goes away in C++17.

Using a range library

All those adaptations of member functions are specific to the STL algorithms library. Indeed, in other libraries such as range-v3 for example, the library directly deals with the case of a member function:

auto results = inputs | ranges::view::transform(&X::getValue); // compiles OK

The above is the counterpart of std::transform in the range-v3 library.

To know more about the fascinating topic of ranges, check out this introduction to the C++ ranges library, or if you prefer written contents to videos, have a look at my guest post on ranges on SimplifyC++!

You will also like

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin