How to Pass Class Member Functions to STL Algorithms
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::function
s 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 bystd::mem_fun_ref
accepts only one parameter (the object of the class) whereas the one generated bystd::mem_fn
also accepts additional parameters that it forwards to the class method.std::mem_fn
therefore allows to use class methods with arguments, whereasstd::mem_fun_ref
doesn’t. - the name “
mem_fun_ref
” is even weirder than “mem_fn
“. Perhapsmem_fn
should have been namedmem_fun
for member function, but I guess it wasn’t because this name was already taken bystd::mem_fun
, a sibling ofstd::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
- std::transform, a central algorithm
- The World Map of C++ STL Algorithms
- The STL Learning Resource
- Introduction to the C++ Ranges Library
Share this post!