我真的很喜欢使用ranget TS的实现cmcstl2。我特别喜欢每个STL算法上的可选投影。像这样转发(不是ehm ...)的Invocable类型:(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));
}

作为引用:range-v3库是这样实现的:(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));
        }
};

现在,我尝试了解两种方法的区别和理由。
无论如何,为什么我应该按值来获取Invocable类型?
为什么我不应该对这些类型使用完美转发?

我比第二种方法更了解第二种方法,因为我了解按值获取接收器参数的方法。

最佳答案

两个原因:

  • 我对标准库规范的阅读是,算法可以复制用户函数对象任意多次,但可以指定它们在单个实例上执行所有调用。由于cmcSTL2经常根据其他算法来实现算法,因此满足该要求的最简单方法是通过reference_wrapper在内部传递函数对象。例如,binary_search调用lower_bound,然后确定由下限表示的元素是否完全匹配。它将reference_wrapper传递给比较对象,将项目函数对象传递给lower_bound,以便以后可以调用相同的实例。
  • 大型和/或可变的函数对象可能很少见,但是没有理由在标准库中对它们的支持很差。复制通常很便宜,移动几乎也总是很便宜,但通过引用传递“绝对”不会昂贵。 cmcSTL2最小化了两个拷贝中用户功能对象的移动。 (此处的空引号表示“按引用传递”会给优化器带来相当大的负担,如果别名分析被功能对象引用所混淆,则在极少数情况下,优化器的负担将大大增加,编译时间并可能生成不良代码。)

    这种推理有一些明显的漏洞。对我来说,最重要的是“如果函数对象可能有用地是有状态的,那么算法不应该像std::for_each一样返回它们以保留该状态吗?” cmcSTL2的设计实质上违反了编程元素所说的“有用返回法则”。我们是否应该使标准算法的签名复杂化,以返回多达三个功能对象(例如一个比较器和两个投影)以容纳0.1%的用例?我认为这里的明显答案是“否”,尤其是考虑到解决方法非常简单:传递reference_wrapper

    那么,当变通办法类似地是通过std::for_each传递时,为什么通常cmcSTL2尤其是标准C++的reference_wrapper会竭尽全力容纳大型和/或可变的函数对象?当cmcSTL2的设计者让std::for_each返回其函数对象时,似乎在这里犯了与LWG相同的错误。

  • 10-08 11:33