Pass function object to std algorithm by reference
Wouldn't it be better to pass functional objects into STL algorithms by forwarding references rather than by value? It will allow to take advantage of the reference qualifier of operator ()
the passed function object .
There are several std::for_each
algorithms about the problem SO( 1 , 2 ), which are problems with changes in the observable state passed to the function object std::for_each
.
Even for algorithms that cannot return a function object (because they should return the last value of the output iterator or something), for those algorithms that cannot return a function object, passing the function object by lvalue reference solves the problem.
For example, the algorithm std::for_each
can be changed to (copied from libc++):
template<typename _InputIterator, typename _Function>
_Function
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
{
for (; __first != __last; ++__first)
__f(*__first);
return _GLIBCXX_MOVE(__f);
}
to:
template<typename _InputIterator, typename _Function>
_Function &&
for_each(_InputIterator __first, _InputIterator __last, _Function && __f)
{
for (; __first != __last; ++__first)
_GLIBCXX_FORWARD(_Function, __f)(*__first);
return _GLIBCXX_FORWARD(_Function, __f);
}
Or (if such breaking changes are allowed) std::for_each
can go back void
without loss of functionality . Similar changes can be made std::forward
for all the rest <numeric>
and <algorithm>
algorithms (from pass-by-value to pass-by-reference, and all calls to call -ed function objects, not just non-const-lvalues) .
I'm aware of a partial workaround: wrapping std::ref
(or std::cref
coercing const this
) the object around , but there is a problem with forwarding operator ()
cv-ref-qualifiers from wrapped functional objects to wrapped objects operator ()
.
Another workaround is to explicitly specify the reference parameter type in the template parameter list of alorithm, but the current Function
parameter sadly always follows the InputIterator
sum parameter in the list OutputIterator
:
#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>
#include <utility>
#include <cstdlib>
int main()
{
std::list< int > l{{1, 2, 3, 4}};
std::copy_n(std::cbegin(l), l.size(), std::ostream_iterator< int >(std::cout, " "));
std::cout << std::endl;
struct F
{
int state;
void operator () (int i) { state += i; }
} f{0};
using I = std::list< int >::const_iterator;
std::for_each< I, F && >(std::cbegin(l), std::cend(l), std::move(f));
std::cout << f.state << std::endl;
return EXIT_SUCCESS;
}
By the way, the change will allow non-copyable and/or non-movable function objects to be passed to algorithms that don't wrap them.
Wouldn't it be better to pass functional objects into STL algorithms by forwarding references rather than by value?
Yes, it would be better this way. It would be better if it was necessary to require that the functor does not have to be , or . But the standard clearly states in 25.1:CopyConstructible
CopyAssignable
MoveConstructible
MoveAssignable
Note: Unless otherwise specified, algorithms that take function objects as arguments are allowed to copy those function objects freely. Programmers where object identity is important should consider using a wrapper class that points to an uncopied implementation object, such as
reference_wrapper<T>
(20.9.4) or some equivalent solution. — endnote ]
This issue was called LWG 92 back in 1998 . and added the "note me" referenced above at that time (this note was modified as it didn't exist at the time ).reference_wrapper<T>
This is a great solution for vendors of std::lib, and a great solution for committee members responsible for revising the spec, but for someone like you who wants to use stateful For functor people, this is not a good choice.
Of course, at the time there was no forwarding reference as a possible solution. Also at that time, it was common for std::implementation to pass functors by value in algorithms , which would further corrupt their state (as indicated by LWG 92 's specification) .
You have correctly touched all the points related to this question:
Clients could use
std::ref
instead , but this would not respect reference-qualified functors.Clients can explicitly specify functor reference parameters, but this does not prohibit implementations from duplicating functors within an algorithm .
Explicitly specifying functor reference parameters is very inconvenient for the client because they always come last in the template parameter list.
First, libc++ is the only std::implementation written that forbids copying functors internally. That is, if you write an 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 << '}' << '\n';
}
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 result today is:
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 stillNth
Internal reproduction , remove_if
as described by Nico Josuttis 18 years ago.
Change the code to:
Nth pred{3};
auto pos = std::remove_if(coll.begin(), coll.end(), std::ref(pred));
does change the result to:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}
IMHO it's just a runtime error waiting for programmers unfamiliar with std::lib's long history to do it.