Isn't it better to pass function objects into the STL algorithms by
forwarding reference rather then by value?
Yes, it would be better. And it would be better if there were a requirement that the functor need not be CopyConstructible
, CopyAssignable
, MoveConstructible
or MoveAssignable
. However the standard specifically says in 25.1:
Note: Unless otherwise specified, algorithms that take function
objects as arguments are permitted to copy those function objects
freely. Programmers for whom object identity is important should
consider using a wrapper class that points to a noncopied
implementation object such as reference_wrapper<T>
(20.9.4), or some
equivalent solution. — end note]
This issue was considered all the way back in 1998 as LWG 92. And at that time the Note I quote above was added (the Note has since been modified as reference_wrapper<T>
didn't exist at the time).
This was a good resolution for vendors of the std::lib, and a good resolution for members of the committee who had the job of fixing the specification, but not so much for people such as yourself wanting to use stateful functors.
And of course, at that time, forwarding references weren't available as a possible solution. Also at that time, it was common for std::implementations to pass the functor around by value within an algorithm, which would further destroy its state (as demonstrated in the description of LWG 92.
You have correctly touched upon all of the points connected to this issue:
Clients can use std::ref
instead, but this won't respect reference-qualified functors.
Clients can explicitly specify functor reference parameters, but this won't prohibit implementations from copying the functor within the algorithm.
Explicitly specifying functor reference parameters is extremely inconvenient for the client since they are always ordered last in the template parameter list.
Fwiw, libc++ is the only std::implementation that was written which forbade itself from internally copying functors. I.e. if you code up the LWG 92 example:
#include <algorithm>
#include <iostream>
#include <list>
#include <numeric>
template <class C>
void
display(const C& c)
{
std::cout << '{';
if (!c.empty())
{
auto i = c.begin();
std::cout << *i;
for (++i; i != c.end(); ++i)
std::cout << ", " << *i;
}
std::cout << '}' << '
';
}
class Nth { // function object that returns true for the nth element
private:
int nth; // element to return true for
int count; // element counter
public:
Nth (int n) : nth(n), count(0) {
}
bool operator() (int) {
return ++count == nth;
}
};
int
main()
{
std::list<int> coll(10);
std::iota(coll.begin(), coll.end(), 0);
display(coll);
auto pos = std::remove_if(coll.begin(), coll.end(), Nth{3});
coll.erase(pos, coll.end());
display(coll);
}
The results today are:
libc++
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}
g++
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 6, 7, 8, 9}
VS-2015
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 6, 7, 8, 9}
g++'s libstdc++ and VS-2015 are still copying Nth
internal to remove_if
just as described 18 years ago by Nico Josuttis.
Changing the code to:
Nth pred{3};
auto pos = std::remove_if(coll.begin(), coll.end(), std::ref(pred));
does portably change the results to:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}
Imho, this is just a run-time error waiting to happen to programmers not familiar with the long history of the std::lib.