std::function
对象的一种好方法是什么?只要签名可以组合在一起,就可以构成功能。示例:template<typename ... Fs>
struct compose_impl
{
compose_impl(Fs&& ... fs) : functionTuple(std::forward_as_tuple(fs ...)) {}
template<std::size_t> struct int2type{};
template<size_t N, typename ... Ts>
auto apply(int2type<N>, Ts&& ... ts)
{
return std::get<N>(functionTuple)(apply(int2type<N+1>(),std::forward<Ts>(ts)...));
}
static const size_t size = sizeof ... (Fs);
template<typename ... Ts>
auto apply(int2type<size-1>, Ts&& ... ts)
{
return std::get<size-1>(functionTuple)(std::forward<Ts>(ts)...);
}
template<typename ... Ts>
auto operator()(Ts&& ... ts)
{
return apply(int2type<0>(), std::forward<Ts>(ts)...);
}
std::tuple<Fs ...> functionTuple;
};
template<typename ... Fs>
auto compose(Fs&& ... fs)
{
return compose_impl<Fs ...>(std::forward<Fs>(fs) ...);
}
欢迎提出改进意见和建议!
#1 楼
一般而言,它确实做得很好,原因有几个:std::tuple
通常利用空基类优化,这意味着由于您输入了lambda,所以您的类通常几乎一无所获,并且所有内容都正确转发。我唯一可以改进的是以下内容:您可以
const
-qualify apply
和operator()
。更清楚的是它是一个编译时常量。在限定
size
时应该保持一致:使用前缀static constexpr
或保留它,但保持一致。如您所见,这些真正的小改进。我也有一些其他评论,但这些评论将比实际建议更多:
static const
在标准中已经存在,并被命名为std::size_t
。但是,我将承认它为该类型使用了另一个模板参数,并且它可能对于您的需求来说太冗长。由于递归是按升序排列的,所以我很难理解您的递归是如何工作的。由于某种原因,我更习惯于降序排列。我本应为
std::
而不是int2type
重载std::integral_constant
并执行递归递归。那样我就可以这样写:template<typename ... Ts>
auto operator()(Ts&& ... ts)
{
return apply(int2type<sizeof ... (Fs) - 1>(), std::forward<Ts>(ts)...);
}
然后,
apply
不必再成为该类的成员。但是我必须承认这是一种意见,而不是指导方针。您的代码足够好,我几乎看不到任何可以改进的地方:) 评论
\ $ \ begingroup \ $
感谢您的评论(尤其是空基类注释和std :: integral_constant,不知道这一点)。我将结合您的所有建议,除了其他递归方向外(...我首先要弄清楚;-))。
\ $ \ endgroup \ $
– davidhigh
2014-09-25 13:51
#2 楼
为了完整起见,这里是对以上代码的修订,其中包含了Morwenn的详尽建议:template<typename ... Fs>
struct compose_impl
{
compose_impl(Fs&& ... fs) : functionTuple(std::forward<Fs>(fs) ...) {}
template<size_t N, typename ... Ts>
auto apply(std::integral_constant<size_t, N>, Ts&& ... ts) const
{
return apply( std::integral_constant<size_t, N - 1>{}
, std::get<N>(functionTuple)(std::forward<Ts>(ts)...));
}
template<typename ... Ts>
auto apply(std::integral_constant<size_t, 0>, Ts&& ... ts) const
{
return std::get<0>(functionTuple)(std::forward<Ts>(ts)...);
}
template<typename ... Ts>
auto operator()(Ts&& ... ts) const
{
return apply(std::integral_constant<size_t, sizeof ... (Fs) - 1>{}, std::forward<Ts>(ts) ...);
}
std::tuple<Fs ...> functionTuple;
};
template<typename ... Fs>
auto compose(Fs&& ... fs)
{
return compose_impl<Fs ...>(std::forward<Fs>(fs) ...);
//possibly also
//return compose_impl<std::decay_t<Fs> ...>(std::forward<Fs>(fs) ...);
// ^^^^^^^^^^^^^^^
// if you want to have copies of the
// functions instead of references
}
评论
您能编辑一下并发表一些有关您这样做的动机的信息吗? (因为组成int f(int)和int g(int)函数的最简单方法是auto h = [&](int x){return f(g(x));};)。您所说的“组成”应该确定这是否是一个好的解决方案。动机很简单,就像我写的一样:将多个函数对象组合成一个对象(当然,我想到的是我的特定应用程序,我猜这里并不重要)。到目前为止,我还没有想到过关于lambda的问题,因此感谢您的提示,但是我同意使用通用lambda可以用更少(和更少复杂)的代码获得相同的普遍性。