How to forward Invocable types correctly

I really like to use cmcstl2 , an implementation of TS ranges. I especially like the optional projections on each STL algorithm. Invocable types are passed (ehm ... or not) as follows: ( 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)); } 

By reference: the range-v3 library implements it as follows: ( 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 am trying to understand the difference and reasoning of both approaches. Why should I accept Invocable types by value anyway? Why shouldn't I use perfect forwarding for these types?

I understand the second approach more than the first, as I understand the methodology for accepting immersion arguments at a cost.

+6
source share
2 answers

Two reasons:

  • My reading of the standard library specification is that algorithms can copy custom function objects as many times as they like, but are specified to make all calls in one instance. Since cmcstl2 often implements algorithms in terms of other algorithms, the easiest way to satisfy this requirement is to internally pass function objects using reference_wrapper . For example, binary_search calls lower_bound and then determines if the element indicated by the lower bound is an exact match. It passes reference_wrapper to matching objects and project objects in lower_bound so that it can call the same instances later.

  • Large and / or mutable function objects may be rare, but there is no reason why they should be poorly maintained in the standard library. Copying is usually cheap, and moving almost always happens, but linking is never expensive. cmcstl2 minimizes both copies of the movement of custom function objects. (The air quotes "never" to indicate that passing by reference leads to a significantly heavier load on the optimizer, increasing compilation time and potentially generating bad code in angular cases if the analysis of aliases confused references to object objects.)

There are some obvious flaws in these considerations. First of all, to me: "If functional objects can be useful from the point of view of state, should algorithms not return them to save this state, as std::for_each ?" The design of cmcstl2 essentially violates what Elements of Programming calls the Law of Useful Returns. Should we complicate the signatures of standard algorithms for returning as many as three functional objects - say, a comparator and two forecasts - to accommodate a 0.1% use case? I think the obvious answer here is no, especially considering the workaround is so simple: pass reference_wrapper .

So, why cmcstl2 in general - and Standard C ++ std::for_each in particular - are not suitable for placing large and / or mutable function objects when a workaround is passed reference_wrapper same way? The cmcstl2 designer seems to have made the same mistake as the LWG when they made std::for_each return their function object.

+8
source

It is traditional to take Invocable by value because they have a small sizeof , for example, in a function pointer or a lambda with several captures. Such functional parameters, in accordance with ABI, are transmitted in machine registers or completely excluded in the case of less or identity . On the other hand, passing by reference seeks to force the compiler to place the real object in RAM.

Larger objects or objects with significant volatile state can be passed through std::ref . The resulting std::reference_wrapper trivially copied and as large as a pointer, so it is effectively passed by value.

+7
source

Source: https://habr.com/ru/post/1015132/


All Articles