I really like to use cmcstl2, an implementation of the Ranges TS. I especially like the optional projections on every STL-algorithm. Invocable
types get forwarded (ehm... or not) like this: (min_element.hpp)
template <ForwardIterator I, Sentinel<I> S,
class Comp = less<>, class Proj = identity>
requires
IndirectStrictWeakOrder<
Comp, projected<I, Proj>>()
I min_element(I first, S last, Comp comp = Comp{}, Proj proj = Proj{});
template <ForwardRange Rng, class Comp = less<>, class Proj = identity>
requires
IndirectStrictWeakOrder<
Comp, projected<iterator_t<Rng>, Proj>>()
safe_iterator_t<Rng>
min_element(Rng&& rng, Comp comp = Comp{}, Proj proj = Proj{})
{
return __stl2::min_element(__stl2::begin(rng), __stl2::end(rng),
__stl2::ref(comp), __stl2::ref(proj));
}
As reference: The range-v3 library implements it like this: (min_element.hpp)
struct min_element_fn {
template<typename I, typename S, typename C = ordered_less, typename P = ident,
CONCEPT_REQUIRES_(ForwardIterator<I>() && Sentinel<S, I>() &&
IndirectRelation<C, projected<I, P>>())>
I operator()(I begin, S end, C pred = C{}, P proj = P{}) const;
template<typename Rng, typename C = ordered_less, typename P = ident,
typename I = range_iterator_t<Rng>,
CONCEPT_REQUIRES_(ForwardRange<Rng>() &&
IndirectRelation<C, projected<I, P>>())>
range_safe_iterator_t<Rng> operator()(Rng &&rng, C pred = C{}, P proj = P{}) const
{
return (*this)(begin(rng), end(rng), std::move(pred), std::move(proj));
}
};
Now I try to understand the difference and reasoning of both approaches.
Why should I take Invocable
types by value anyway?
Why should I not use perfect forwarding for these types?
I understand the second approach more than I do for the first one, since I understand the methodology of taking sink arguments by value.
Two reasons:
My reading of the standard library specification is that algorithms may copy user function objects as many times as they like, but are specified to perform all invocations on a single instance. Since cmcstl2 frequently implements algorithms in terms of other algorithms, the simplest way to meet that requirement is to internally pass function objects by reference_wrapper
. For example, binary_search
calls lower_bound
and then determines if the element denoted by the lower bound is an exact match. It passes reference_wrapper
s to the comparison and project function objects to lower_bound
so that it can invoke the same instances later.
Large and/or mutable function objects may be rare, but there's no reason they must be poorly supported in the standard library. Copying is usually cheap, and moving almost always is as well, but passing by reference is "never" expensive. cmcstl2 minimizes both copies an moves of user function objects. (The air quotes on "never" are there to indicate that passing by reference puts a substantially heavier load on the optimizer, increasing compile times and potentially generating poor code in corner cases if alias analysis is confused by the function object references.)
There are some obvious holes in this reasoning. Foremost to me is "If function objects may usefully be stateful, shouldn't the algorithms be returning them to preserve that state, as does std::for_each
?" The design of cmcstl2 essentially violates what Elements of Programming calls "The Law of Useful Return." Should we complicate the signatures of the standard algorithms to return as many as three function objects - say a comparator and two projections - to accommodate a 0.1% use case? I think the obvious answer here is "no," especially given that the workaround is so simple: pass a reference_wrapper
.
So, why should cmcstl2 in general - and Standard C++'s std::for_each
in particular - go out of their way to accommodate large and/or mutable function objects when the workaround similarly is to pass a reference_wrapper
? It seems the designer of cmcstl2 has made the same mistake here as did LWG when they made std::for_each
return its function object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With