当试图用另一个lambda组成一个curried lambda时出乎意料的结果

Mor*_*enn 12 c++ lambda indices c++11

我正在玩C++ 11 lambdas,并试图模仿D编程语言functional模块中的一些函数.我实际上是想实现currycompose.这是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;
}
Run Code Online (Sandbox Code Playgroud)

问题是我从g ++和clang ++得不到相同的结果.我明白了:

  • 35与g ++ 4.8.1
  • 25与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());
}
Run Code Online (Sandbox Code Playgroud)

这是我的实现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());
}
Run Code Online (Sandbox Code Playgroud)

该类function_trait用于获取arda,返回类型和lambda参数的类型.这段代码很大程度上依赖于索引技巧.由于我不使用C++ 14,我不使用std::index_sequence名称下的旧实现indices.indices_range<begin, end>是与范围对应的索引序列[begin, end).您可以在代码的在线版本中找到这些辅助元函数(以及currycompose)的实现,但它们在此问题中的意义不大.


由于编译器错误,我是否有执行curry和/或compose错误结果的错误(使用g ++ 4.8.1和clang ++ 3.5)?


编辑:您可能会发现上面的代码不太可读.因此,这里有版本curry,并compose认为是完全一样的,但使用别名模板来减少样板.我也删除了static_asserts; 虽然它们可能是有用的信息,但这个问题的文本太多而且它们并没有参与到手头的问题中.

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());
}
Run Code Online (Sandbox Code Playgroud)

vmr*_*rob 3

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

auto add5 = curry(add, 5);
Run Code Online (Sandbox Code Playgroud)

然后,在函数的调用中curry,您将在堆栈上创建该变量的副本作为参数之一:

auto curry(Function&& func, First first)
Run Code Online (Sandbox Code Playgroud)

然后,在您的调用中curry_impl传递一个对 的引用,first一旦您的调用完成,该引用将不存在curry。由于您生成的 lambda 使用对不再存在的变量的引用,因此您会得到未定义的行为。

要解决您遇到的问题,只需更改 的原型curry以使用通用引用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());
}
Run Code Online (Sandbox Code Playgroud)

然后在主要部分:

int foo = 5;
auto add5 = curry(add, foo);
Run Code Online (Sandbox Code Playgroud)

当然,将自己限制为左值表达式对于接口来说是一个相当大的问题,因此值得一提的是,如果您计划在练习之外使用它,那么提供一个可以使用右值的接口将是一个好主意。

然后,我会再次更改它,以便生成的仿函数拥有其组件的副本std::bind。我知道如果以下代码不起作用,我会有点困惑:

std::function<int(int)> foo()
{
    std::function<int(int, int)> add = [](int a, int b)
    {
        return a + b;
    };
    return curry(add, 5);
}
Run Code Online (Sandbox Code Playgroud)

编辑:我现在看到某些版本的 gcc 仍然要求将值按值捕获到生成的巴中。GCC 4.9.0 20131229 是我测试过的版本,运行良好。

编辑#2:为每个Xeo指定正确的用法

  • 我同意您应该像标准库功能部分一样使用默认复制。然后,您可以选择使用“std::ref”/“std::cref”之类的内容来引用语义。IMO,这会减少容易出错的代码,因为只有明确使用“std::ref”等,您才能得到生命周期问题。 (2认同)