如何正确转发可调用类型
How to properly forward Invocable types
我很喜欢用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));
}
作为参考: 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));
}
};
现在我试着理解这两种方法的区别和原因。
为什么我应该按值取 Invocable
类型?
为什么我不应该对这些类型使用完美转发?
与第一种方法相比,我更了解第二种方法,因为我了解按值获取接收器参数的方法。
按值取 Invocable
是传统做法,因为它们往往具有较小的 sizeof
,例如在函数指针或具有少量捕获的 lambda 中。根据 ABI,此类函数参数在机器寄存器中传递,或者在 less
或 identity
的情况下完全消除。另一方面,通过引用传递往往会促使编译器将真实对象放入 RAM。
较大的对象或具有重要可变状态的对象可能会通过 std::ref
。结果 std::reference_wrapper
是 trivially-copyable 并且和指针一样大,所以它是按值有效传递的。
两个原因:
我对标准库规范的理解是,算法可以根据需要多次复制用户函数对象,但被指定为在单个实例上执行所有调用。由于 cmcstl2 经常根据其他算法来实现算法,因此满足该要求的最简单方法是通过 reference_wrapper
在内部传递函数对象。例如,binary_search
调用 lower_bound
,然后确定下限表示的元素是否完全匹配。它将 reference_wrapper
s 传递给比较对象,并将项目函数对象传递给 lower_bound
,以便稍后可以调用相同的实例。
大型 and/or 可变函数对象可能很少见,但没有理由在标准库中对它们的支持不佳。复制通常很便宜,移动几乎总是如此,但通过引用传递是 "never" 昂贵的。 cmcstl2 最小化用户函数对象的副本和移动。 ("never" 上的空引号表示通过引用传递会给优化器带来更重的负载,增加编译时间,并且如果别名分析被函数对象引用混淆,可能会在极端情况下生成糟糕的代码。)
这个推理中有一些明显的漏洞。对我来说最重要的是 "If function objects may usefully be stateful, shouldn't the algorithms be returning them to preserve that state, as does std::for_each
?" cmcstl2 的设计本质上违反了编程基础所称的内容 "The Law of Useful Return." 我们是否应该将标准算法的签名复杂化为 return 多达三个函数对象 - 比如说一个比较器和两个预测——容纳 0.1% 的用例?我认为这里明显的答案是 "no," 特别是考虑到解决方法非常简单:传递 reference_wrapper
.
那么,为什么一般的 cmcstl2 - 尤其是标准 C++ std::for_each
- 不遗余力地容纳大型 and/or 可变函数对象,而解决方法同样是传递一个 reference_wrapper
?似乎 cmcstl2 的设计者在这里犯了与 LWG 一样的错误,他们将 std::for_each
return 作为函数对象。
我很喜欢用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));
}
作为参考: 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));
}
};
现在我试着理解这两种方法的区别和原因。
为什么我应该按值取 Invocable
类型?
为什么我不应该对这些类型使用完美转发?
与第一种方法相比,我更了解第二种方法,因为我了解按值获取接收器参数的方法。
按值取 Invocable
是传统做法,因为它们往往具有较小的 sizeof
,例如在函数指针或具有少量捕获的 lambda 中。根据 ABI,此类函数参数在机器寄存器中传递,或者在 less
或 identity
的情况下完全消除。另一方面,通过引用传递往往会促使编译器将真实对象放入 RAM。
较大的对象或具有重要可变状态的对象可能会通过 std::ref
。结果 std::reference_wrapper
是 trivially-copyable 并且和指针一样大,所以它是按值有效传递的。
两个原因:
我对标准库规范的理解是,算法可以根据需要多次复制用户函数对象,但被指定为在单个实例上执行所有调用。由于 cmcstl2 经常根据其他算法来实现算法,因此满足该要求的最简单方法是通过
reference_wrapper
在内部传递函数对象。例如,binary_search
调用lower_bound
,然后确定下限表示的元素是否完全匹配。它将reference_wrapper
s 传递给比较对象,并将项目函数对象传递给lower_bound
,以便稍后可以调用相同的实例。大型 and/or 可变函数对象可能很少见,但没有理由在标准库中对它们的支持不佳。复制通常很便宜,移动几乎总是如此,但通过引用传递是 "never" 昂贵的。 cmcstl2 最小化用户函数对象的副本和移动。 ("never" 上的空引号表示通过引用传递会给优化器带来更重的负载,增加编译时间,并且如果别名分析被函数对象引用混淆,可能会在极端情况下生成糟糕的代码。)
这个推理中有一些明显的漏洞。对我来说最重要的是 "If function objects may usefully be stateful, shouldn't the algorithms be returning them to preserve that state, as does std::for_each
?" cmcstl2 的设计本质上违反了编程基础所称的内容 "The Law of Useful Return." 我们是否应该将标准算法的签名复杂化为 return 多达三个函数对象 - 比如说一个比较器和两个预测——容纳 0.1% 的用例?我认为这里明显的答案是 "no," 特别是考虑到解决方法非常简单:传递 reference_wrapper
.
那么,为什么一般的 cmcstl2 - 尤其是标准 C++ std::for_each
- 不遗余力地容纳大型 and/or 可变函数对象,而解决方法同样是传递一个 reference_wrapper
?似乎 cmcstl2 的设计者在这里犯了与 LWG 一样的错误,他们将 std::for_each
return 作为函数对象。