使用C ++ lambda进行部分应用?

时间:2012-07-24 11:58:35

标签: c++ lambda c++11 currying

编辑:我在下面使用咖喱,但已被告知这是部分申请。

我一直想弄清楚如何在C ++中编写咖喱函数,我实际上已经弄明白了!

#include <stdio.h>
#include <functional>

template< class Ret, class Arg1, class ...Args >
auto curry(  Ret f(Arg1,Args...), Arg1 arg )
    -> std::function< Ret(Args...) >
{
    return [=]( Args ...args ) { return f( arg, args... ); };
}

我也为lambdas写了一个版本。

template< class Ret, class Arg1, class ...Args >
auto curry(  const std::function<Ret(Arg1,Args...)>& f, Arg1 arg )
    -> std::function< Ret(Args...) >
{
    return [=]( Args ...args ) { return f( arg, args... ); };
}

测试:

int f( int x, int y )
{
    return x + y;
}

int main()
{
    auto f5 = curry( f, 5 );
    auto g2 = curry( std::function<int(int,int)>([](int x, int y){ return x*y; }), 2 );
    printf("%d\n",f5(3));
    printf("%d\n",g2(3));
}

呸!初始化g2的线很大,我也可以手动调整它。

auto g2 = [](int y){ return 2*y; };

短得多。但是由于意图是有一个非常通用和方便的咖喱函数,我可以(1)写一个更好的函数或(2)我的lambda以某种方式隐式构造一个std :: function?我担心当f不是自由函数时,当前版本违反了最小惊喜的规则。特别令人讨厌的是我所知道的make_function或类似功能似乎不存在。真的,我理想的解决方案只是调用std :: bind,但我不确定如何将它与变量模板一起使用。

PS:请不要提升,但如果没别的话,我会解决。

编辑:我已经了解了std :: bind。如果std :: bind完全符合我想要的最佳语法,我就不会写这个函数。这应该是一个特殊情况,它只绑定第一个元素。

正如我所说,我理想的解决方案应该使用绑定,但如果我想使用它,我会使用它。

4 个答案:

答案 0 :(得分:10)

您的curry函数只是std::bind的缩小效率低的子字节(std::bind1stbind2nd现在我们不能再使用std::result_of

你的两行实际上是

auto f5 = std::bind(f, 5, _1);
auto g2 = std::bind(std::multiplies<int>(), 2, _1);

使用namespace std::placeholders后。这样可以小心地避免装入std::function,并允许编译器更容易地在调用站点内联结果。

对于两个参数的功能,黑客攻击

auto bind1st(F&& f, T&& t) 
    -> decltype(std::bind(std::forward<F>(f), std::forward<T>(t), _1))
{
    return std::bind(std::forward<F>(f), std::forward<T>(t), _1)
}

可能有效,但很难概括为可变参数(你最终会重写std::bind中的大量逻辑)。

也是currying不是部分应用。 Currying有“签名”

((a, b) -> c) -> (a -> b -> c)

即。它是将一个函数转换为一个返回函数的函数的动作。它有一个反uncurry执行反向操作(对于数学家:curryuncurry是同构,并定义一个附加)。使用C ++编写这种反向非常麻烦(提示:使用std::result_of)。

答案 1 :(得分:8)

这是一种在C ++中使用currying的方法,在最近对OP进行编辑后可能会或可能不相关。

由于超载,检查仿函数并检测其算法是非常有问题的。但是,如果给定一个仿函数f和一个参数a,我们可以检查f(a)是否是一个有效的表达式。如果不是,我们可以存储a并给出以下参数b,我们可以检查f(a, b)是否是有效表达式,依此类推。即:

#include <utility>
#include <tuple>

/* Two SFINAE utilities */

template<typename>
struct void_ { using type = void; };

template<typename T>
using Void = typename void_<T>::type;

// std::result_of doesn't play well with SFINAE so we deliberately avoid it
// and roll our own
// For the sake of simplicity this result_of does not compute the same type
// as std::result_of (e.g. pointer to members)
template<typename Sig, typename Sfinae = void>
struct result_of {};

template<typename Functor, typename... Args>
struct result_of<
    Functor(Args...)
    , Void<decltype( std::declval<Functor>()(std::declval<Args>()...) )>
> {
    using type = decltype( std::declval<Functor>()(std::declval<Args>()...) );
};

template<typename Functor, typename... Args>
using ResultOf = typename result_of<Sig>::type;

template<typename Functor, typename... Args>
class curry_type {
    using tuple_type = std::tuple<Args...>;
public:
    curry_type(Functor functor, tuple_type args)
        : functor(std::forward<Functor>(functor))
        , args(std::move(args))
    {}

    // Same policy as the wrappers from std::bind & others:
    // the functor inherits the cv-qualifiers from the wrapper
    // you might want to improve on that and inherit ref-qualifiers, too
    template<typename Arg>
    ResultOf<Functor&(Args..., Arg)>
    operator()(Arg&& arg)
    {
        return invoke(functor, std::tuple_cat(std::move(args), std::forward_as_tuple(std::forward<Arg>(arg))));
    }

    // Implementation omitted for brevity -- same as above in any case
    template<typename Arg>
    ResultOf<Functor const&(Args..., Arg)>
    operator()(Arg&& arg) const;

    // Additional cv-qualified overloads omitted for brevity

    // Fallback: keep calm and curry on
    // the last ellipsis (...) means that this is a C-style vararg function
    // this is a trick to make this overload (and others like it) least
    // preferred when it comes to overload resolution
    // the Rest pack is here to make for better diagnostics if a user erroenously
    // attempts e.g. curry(f)(2, 3) instead of perhaps curry(f)(2)(3)
    // note that it is possible to provide the same functionality without this hack
    // (which I have no idea is actually permitted, all things considered)
    // but requires further facilities (e.g. an is_callable trait)
    template<typename Arg, typename... Rest>
    curry_type<Functor, Args..., Arg>
    operator()(Arg&& arg, Rest const&..., ...)
    {
        static_assert( sizeof...(Rest) == 0
                       , "Wrong usage: only pass up to one argument to a curried functor" );
        return { std::forward<Functor>(functor), std::tuple_cat(std::move(args), std::forward_as_tuple(std::forward<Arg>(arg))) };
    }

    // Again, additional overloads omitted

    // This is actually not part of the currying functionality
    // but is here so that curry(f)() is equivalent of f() iff
    // f has a nullary overload
    template<typename F = Functor>
    ResultOf<F&(Args...)>
    operator()()
    {
        // This check if for sanity -- if I got it right no user can trigger it
        // It *is* possible to emit a nice warning if a user attempts
        // e.g. curry(f)(4)() but requires further overloads and SFINAE --
        // left as an exercise to the reader
        static_assert( sizeof...(Args) == 0, "How did you do that?" );
        return invoke(functor, std::move(args));
    }

    // Additional cv-qualified overloads for the nullary case omitted for brevity

private:
    Functor functor;
    mutable tuple_type args;

    template<typename F, typename Tuple, int... Indices>
    ResultOf<F(typename std::tuple_element<Indices, Tuple>::type...)>
    static invoke(F&& f, Tuple&& tuple, indices<Indices...>)
    {
        using std::get;
        return std::forward<F>(f)(get<Indices>(std::forward<Tuple>(tuple))...);
    }

    template<typename F, typename Tuple>
    static auto invoke(F&& f, Tuple&& tuple)
    -> decltype( invoke(std::declval<F>(), std::declval<Tuple>(), indices_for<Tuple>()) )
    {
        return invoke(std::forward<F>(f), std::forward<Tuple>(tuple), indices_for<Tuple>());
    }
};

template<typename Functor>
curry_type<Functor> curry(Functor&& functor)
{ return { std::forward<Functor>(functor), {} }; }

如果存在indices类型和indices_for实用程序,则上述代码使用GCC 4.8的快照(禁止复制和粘贴错误)进行编译。 This question及其答案证明了这些事情的需要和实施,其中seq扮演indices的角色,而gens可以用来实现(更方便){{ 1}}。

在涉及价值类别和(可能)临时工作的生命周期时,要特别注意。 indices_for(及其附带的类型,这是一个实现细节)旨在尽可能轻量级,同时仍然使其非常,非常安全地使用。特别是,用法如:

curry

不会复制foo a; bar b; auto f = [](foo a, bar b, baz c, int) { return quux(a, b, c); }; auto curried = curry(f); auto pass = curried(a); auto some = pass(b); auto parameters = some(baz {}); auto result = parameters(0); fa;它也不会导致对临时工的悬挂引用。即使b替换为auto(假设auto&&是理智的,但这超出了quux的控制),这一切仍然适用。在这方面仍然可以提出不同的政策(例如,系统地腐朽)。

请注意,参数(但不是函子)在最终调用中使用与传递给curried包装器时相同的值类别。因此在

curry

这意味着在计算auto functor = curry([](foo f, int) {}); auto curried = functor(foo {}); auto r0 = curried(0); auto r1 = curried(1); 时会将移动的foo传递给基础仿函数。

答案 2 :(得分:4)

使用一些C ++ 14功能,可以以非常简洁的方式实现适用于lambda的部分应用程序。

template<typename _function, typename _val>
auto partial( _function foo, _val v )
{
  return
    [foo, v](auto... rest)
    {
      return foo(v, rest...);
    };
}

template< typename _function, typename _val1, typename... _valrest >
auto partial( _function foo, _val1 val, _valrest... valr )
{
  return
    [foo,val,valr...](auto... frest)
    {
      return partial(partial(foo, val), valr...)(frest...);
    };
}

// partial application on lambda
int p1 = partial([](int i, int j){ return i-j; }, 6)(2);
int p2 = partial([](int i, int j){ return i-j; }, 6, 2)();

答案 3 :(得分:0)

人们提供的许多例子以及我在其他地方看到过的帮助类都可以做任何事情。我意识到,当你这样做时,这就变得微不足道了!

#include <utility> // for declval
#include <array>
#include <cstdio>

using namespace std;

template< class F, class Arg >
struct PartialApplication
{
    F f;
    Arg arg;

    constexpr PartialApplication( F&& f, Arg&& arg )
        : f(forward<F>(f)), arg(forward<Arg>(arg))
    {
    }

    /* 
     * The return type of F only gets deduced based on the number of arguments
     * supplied. PartialApplication otherwise has no idea whether f takes 1 or 10 args.
     */
    template< class ... Args >
    constexpr auto operator() ( Args&& ...args )
        -> decltype( f(arg,declval<Args>()...) )
    {
        return f( arg, forward<Args>(args)... );
    }
};

template< class F, class A >
constexpr PartialApplication<F,A> partial( F&& f, A&& a )
{
    return PartialApplication<F,A>( forward<F>(f), forward<A>(a) );
}

/* Recursively apply for multiple arguments. */
template< class F, class A, class B >
constexpr auto partial( F&& f, A&& a, B&& b )
    -> decltype( partial(partial(declval<F>(),declval<A>()),
                         declval<B>()) )
{
    return partial( partial(forward<F>(f),forward<A>(a)), forward<B>(b) );
}

/* Allow n-ary application. */
template< class F, class A, class B, class ...C >
constexpr auto partial( F&& f, A&& a, B&& b, C&& ...c )
    -> decltype( partial(partial(declval<F>(),declval<A>()),
                         declval<B>(),declval<C>()...) )
{
    return partial( partial(forward<F>(f),forward<A>(a)), 
                    forward<B>(b), forward<C>(c)... );
}

int times(int x,int y) { return x*y; }

int main()
{
    printf( "5 * 2 = %d\n", partial(times,5)(2) );
    printf( "5 * 2 = %d\n", partial(times,5,2)() );
}