2012-07-24 56 views
16

編輯:我使用下面的咖喱,但已被告知這是相反的部分應用。C++ lambda的部分應用程序?

我一直在試圖弄清楚如何用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...); }; 
} 

而且我也爲lambda寫了一個版本。

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的行非常大,以至於我不得不手動對它進行curry。

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

更短。但是,因爲意圖是要有一個真正通用和方便的咖喱函數,我可以(1)寫一個更好的函數或(2)以某種方式我的lambda隱式地構造一個std ::函數?當f不是一個自由函數時,我擔心現在的版本違反了最不可思議的規則。特別煩人的是,我所知道的make_function或類似函數似乎是不存在的。真的,我的理想解決方案只是對std :: bind的調用,但我不知道如何將它與variadic模板一起使用。 PS:沒有提升,但我會解決,如果沒有別的。

編輯:我已經知道std :: bind了。如果std :: bind完全符合我想要的最佳語法,我不會寫這個函數。這應該更多的是一個特殊情況,它只綁定第一個元素。

正如我所說,我的理想解決方案應該使用綁定,但如果我想使用它,我會使用它。

+0

你說沒有提升,但爲什麼?如果你不想使用庫,你仍然可以複製它提供的功能。 – mydogisbox 2012-07-24 12:14:59

+0

您的咖喱功能具有綁定功能。你可以替代地使用'自動FN =標準::綁定([](INT的x,int y)對{返回X * Y;},性病::佔位符:: _ 1,5);' – mkaes 2012-07-24 12:19:03

+0

mydogisbox:由於在五年我一直在編碼,我對Boost嗤之以鼻。如果它足夠小,我可以重新實現一些功能,但我不期望Boost的功能很小。此外,其許多功能已成爲標準。然而,我總是願意被證明是錯誤的。 – SplinterOfChaos 2012-07-24 12:35:58

回答

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)()); 
} 
+0

此代碼有點危險,因爲兩者部分應用模板中的F和Arg可以推斷爲引用,如果部分應用的函數用左值調用。 PartialApplication構造函數中的F和Arg都不是通用引用。 std ::轉發它們毫無意義。我只需在構造函數中傳遞by-value和std :: move。在傳遞給PartialApplication模板之前,兩個參數函數partial需要在F和A類型中使用std :: remove_reference。 – Sumant 2015-10-15 00:20:53

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改寫了很多的邏輯) 。

咖喱也不是部分應用。柯里裏有「簽名」

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

ie。這是將帶有兩個參數的函數轉換爲函數返回函數的操作。它有一個逆uncurry執行反向操作(對於數學家:curryuncurry是同構,並定義一個副本)。在C++中編寫這個反函數非常麻煩(提示:使用std::result_of)。

+1

我認爲OP希望避免佔位符的需要。 IIUC,他想要一個可與任何可調用對象配合使用的「bind1st」版本。 'bind'強制你綁定所有的參數,當它們有很多的時候,或者你正在編寫通用的代碼時,這可能是一個負擔。 – 2012-07-24 12:49:43

+2

我想你會讓柯里和不安全感混淆:柯里化將一個n元函數轉化爲一元函數的「鏈條」,而不會發生相反的情況。 – 2012-07-24 12:53:06

+0

@LucTouraille:更正,謝謝。 – 2012-07-24 12:54:52

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), {} }; } 

上面的代碼編譯使用GCC 4.8的快照(禁止拷貝和粘貼錯誤),條件是在一個indices類型和indices_for效用。 This question並且其答案證實了這種事情的需要和實施,其中seq扮演indicesgens的角色可用於實現(更方便)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); 

不復制fab;也不會導致對臨時對象的懸掛引用。這一切都仍然適用,即使auto取代有auto&&(假設quux是理智的,但是這超出curry控制)。在這方面仍然有可能提出不同的政策(例如系統性衰減)。

請注意,參數(但不是函數)在最終調用中傳遞時具有相同的值類別,因爲它們傳遞給curried包裝器。因此,在

auto functor = curry([](foo f, int) {}); 
auto curried = functor(foo {}); 
auto r0 = curried(0); 
auto r1 = curried(1); 

這意味着一個計算r1時被傳遞到底層函子移動-從foo

+0

+1使用[...,...](http://stackoverflow.com/questions/5625600/what-is-the-meaning-of-token) – Cubbi 2012-07-25 12:34:44

3

對於一些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)(); 
+1

'rest'應該是'auto && ...',並用作decltype(rest)(rest)...'not'auto ...'並用作'rest ...'。您還應該在C++ 14中捕捉前進,這會讓您的第二個'partial'超載更復雜。 [現場示例](http://coliru.stacked-crooked.com/a/69e7c7249fa6c5a8)。這避免了你的代碼所做的大量不必要的副本! – Yakk 2015-09-04 00:21:43