我正在玩C++ 11 lambdas,并试图模仿D编程语言的 functional module中的某些功能。我实际上是在尝试实现 curry compose 。这是我要开始工作的main:

int main()
{
    auto add = [](int a, int b)
    {
        return a + b;
    };
    auto add5 = curry(add, 5);

    auto composed = compose(add5, add);
    // Expected result: 25
    std::cout << composed(5, 15) << std::endl;
}

问题是我没有从g++和clang++得到相同的结果。我得到:

带g++ 4.8.1的
  • 35
  • 25 with g++ 4.8.2
  • 带g++ 4.9的
  • 25
  • 带clang++ 3.5的
  • 32787(与Coliru一起使用的行李箱)

  • g++ 4.8.2和4.9给了我预期的结果。从g++ 4.8.1和clang 3.5获得的结果不取决于传递给curry的值。我首先认为这可能是编译器错误,但是更有可能是我的代码中有错误。

    这是我对curry的实现:
    template<typename Function, typename First, std::size_t... Ind>
    auto curry_impl(const Function& func, First&& first, indices<Ind...>)
        -> std::function<
            typename function_traits<Function>::result_type(
            typename function_traits<Function>::template argument_type<Ind>...)>
    {
        return [&](typename function_traits<Function>::template argument_type<Ind>&&... args)
        {
            return func(
                std::forward<First>(first),
                std::forward<typename function_traits<Function>::template argument_type<Ind>>(args)...
            );
        };
    }
    
    template<typename Function, typename First,
             typename Indices=indices_range<1, function_traits<Function>::arity>>
    auto curry(Function&& func, First first)
        -> decltype(curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices()))
    {
        using FirstArg = typename function_traits<Function>::template argument_type<0>;
        static_assert(std::is_convertible<First, FirstArg>::value,
                      "the value to be tied should be convertible to the type of the function's first parameter");
        return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
    }
    

    这是我对compose的实现(请注意,我只写了一个二进制compose,而D一个是可变参数):
    template<typename First, typename Second, std::size_t... Ind>
    auto compose_impl(const First& first, const Second& second, indices<Ind...>)
        -> std::function<
            typename function_traits<First>::result_type(
            typename function_traits<Second>::template argument_type<Ind>...)>
    {
        return [&](typename function_traits<Second>::template argument_type<Ind>&&... args)
        {
            return first(second(
                std::forward<typename function_traits<Second>::template argument_type<Ind>>(args)...
            ));
        };
    }
    
    template<typename First, typename Second,
             typename Indices=make_indices<function_traits<Second>::arity>>
    auto compose(First&& first, Second&& second)
        -> decltype(compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices()))
    {
        static_assert(function_traits<First>::arity == 1u,
                      "all the functions passed to compose, except the last one, must take exactly one parameter");
    
        using Ret = typename function_traits<Second>::result_type;
        using FirstArg = typename function_traits<First>::template argument_type<0>;
        static_assert(std::is_convertible<Ret, FirstArg>::value,
                      "incompatible return types in compose");
    
        return compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices());
    }
    
    function_trait类用于获取Arity,返回类型和Lambda参数的类型。此代码在很大程度上依赖于索引技巧。由于我不使用C++ 14,所以我不使用 std::index_sequence 而是使用indices名称下的一个较旧的实现。 indices_range<begin, end>是与范围[begin, end)相对应的索引序列。您可以找到这些帮助器元功能(以及currycompose)on the online version of the code的实现,但是它们在此问题中的意义不大。

    我在curry和/或compose的实现中是否存在错误,还是由于编译器错误而导致了不好的结果(对于g++ 4.8.1和clang++ 3.5)?

    编辑:您可能会发现上面的代码不太可读。因此,这里是currycompose的版本完全相同,但是使用别名模板来减少样板。我还删除了static_assert;尽管它们可能是有用的信息,但这只是问题的文字,它们并不在手头的问题中发挥作用。
    template<typename Function, typename First, std::size_t... Ind>
    auto curry_impl(const Function& func, First&& first, indices<Ind...>)
        -> std::function<
            result_type<Function>(
            argument_type<Function, Ind>...)>
    {
        return [&](argument_type<Function, Ind>&&... args)
        {
            return func(
                std::forward<First>(first),
                std::forward<argument_type<Function, Ind>>(args)...
            );
        };
    }
    
    template<typename Function, typename First,
             typename Indices=indices_range<1, function_traits<Function>::arity>>
    auto curry(Function&& func, First first)
        -> decltype(curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices()))
    {
        return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
    }
    
    template<typename First, typename Second, std::size_t... Ind>
    auto compose_impl(const First& first, const Second& second, indices<Ind...>)
        -> std::function<
            typename result_type<First>(
            typename argument_type<Second, Ind>...)>
    {
        return [&](argument_type<Second, Ind>&&... args)
        {
            return first(second(
                std::forward<argument_type<Second, Ind>>(args)...
            ));
        };
    }
    
    template<typename First, typename Second,
             typename Indices=make_indices<function_traits<Second>::arity>>
    auto compose(First&& first, Second&& second)
        -> decltype(compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices()))
    {
        return compose_impl(std::forward<First>(first), std::forward<Second>(second), Indices());
    }
    

    最佳答案

    正如我相信其他人在您的评论中提到的那样,与您的代码有关的问题是生命周期问题。请注意,您将第二个参数5作为右值传递给curry:

    auto add5 = curry(add, 5);
    

    然后,在调用curry函数时,您将在堆栈上创建该变量的拷贝作为参数之一:
    auto curry(Function&& func, First first)
    

    然后,在对curry_impl的调用中,您将传递对first的引用,一旦对curry的调用完成,该引用将不存在。当您生成的lambda使用对不再存在的变量的引用时,您将获得未定义的行为。

    要解决您遇到的问题,只需更改curry的原型(prototype),以使用对first的通用引用,并确保不将右值传递给curry:
    template<typename Function, typename First,
             typename Indices=indices_range<1, function_traits<Function>::arity>>
    auto curry(Function&& func, First&& first)
        -> decltype(curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices()))
    {
        using FirstArg = typename function_traits<Function>::template argument_type<0>;
        static_assert(std::is_convertible<First, FirstArg>::value,
                      "the value to be tied should be convertible to the type of the function's first parameter");
        return curry_impl(std::forward<Function>(func), std::forward<First>(first), Indices());
    }
    

    然后在主要:
    int foo = 5;
    auto add5 = curry(add, foo);
    

    当然,将自己限制为左值表达式是该接口(interface)的一个相当大的问题,因此值得一提的是,如果您打算在练习之外使用此值,则最好提供一个可以使用右值的接口(interface)。

    然后再次更改它,以使生成的仿函数像std::bind一样拥有其组件的拷贝。我知道如果以下代码不起作用,我会有些困惑:
    std::function<int(int)> foo()
    {
        std::function<int(int, int)> add = [](int a, int b)
        {
            return a + b;
        };
        return curry(add, 5);
    }
    

    编辑:我现在看到gcc的某些版本仍然需要将值按值捕获到生成的lamba中。我测试了GCC 4.9.0 20131229的构建,可以在其上正常工作。

    编辑2:按Xeo指定正确的用法

    10-06 13:42
    查看更多