用C ++编写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) ...);
}


欢迎提出改进意见和建议!

评论

您能编辑一下并发表一些有关您这样做的动机的信息吗? (因为组成int f(int)和int g(int)函数的最简单方法是auto h = [&](int x){return f(g(x));};)。您所说的“组成”应该确定这是否是一个好的解决方案。

动机很简单,就像我写的一样:将多个函数对象组合成一个对象(当然,我想到的是我的特定应用程序,我猜这里并不重要)。到目前为止,我还没有想到过关于lambda的问题,因此感谢您的提示,但是我同意使用通用lambda可以用更少(和更少复杂)的代码获得相同的普遍性。

#1 楼

一般而言,它确实做得很好,原因有几个:std::tuple通常利用空基类优化,这意味着由于您输入了lambda,所以您的类通常几乎一无所获,并且所有内容都正确转发。我唯一可以改进的是以下内容:


您可以const -qualify applyoperator()。更清楚的是它是一个编译时常量。
在限定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
}