c++ - 尝试将 curried lambda 与另一个 lambda 组合时出现意外结果

标签 c++ c++11 lambda indices

我正在玩弄 C++11 lambda,并试图模仿 functional module 中的一些函数。 D 编程语言。我实际上是在尝试实现 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++ 得到相同的结果。我明白了:

  • 35 与 g++ 4.8.1
  • 25 with g++ 4.8.2
  • 25 与 g++ 4.9
  • 32787 与 clang++ 3.5(与 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());
}

类(class)function_trait用于获取 lambda 的arity、返回类型和参数类型。此代码严重依赖索引技巧。由于我不用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)?


编辑:您可能会发现上面的代码不太可读。所以,这里是 curry 的版本和 compose它们完全相同,但使用别名模板来减少样板。我还删除了 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());
}

然后在main中:

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 指定正确用法

关于c++ - 尝试将 curried lambda 与另一个 lambda 组合时出现意外结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23205241/

相关文章:

c++ - 使用 MPI 通过命令行传递参数

c++ - 2016 年 MSVC 不支持 noexcept 关键字

c++ - 具有可变高阶函数的重载分辨率

c# - 是否可以将反射与 linq to entity 一起使用?

python - 错误 - 此版本的 Visual Studio 无法打开以下项目

c++ - 重载运算符[]多态

c++ - 如何修复: Executing lambda expression using pthread in c++

Python lambda 不记得 for 循环中的参数

c++ - 将 ASCII 值分配给 Bison 中的变量

c++ - 在不更改接口(interface)的情况下向对象添加功能