2016-04-09 45 views
2

所以我在玩GCC6及其概念實現,我認爲Haskell Prelude是一個很好的實驗來源。 Haskell的核心功能之一是函數組合,這是我需要立即解決的問題。在我知道可調用參數之前,如何約束一個懶惰的構圖?

模仿Haskell語法作爲最好的,我可以,我寫了這個功能:

template <typename F, typename G> 
auto operator*(F f, G g) 
{ 
    return [f, g](auto... args) { 
    return f(g(args...)); 
    } 
} 

偉大的工程,讓我做的東西,如:

auto add([](int a, int b) { return a + b; } 
auto doubled([](int a) { return a * 2; } 

auto add_then_double(doubled * add); 
assert(add_then_double(2, 3) == 10); 

快樂,我決定去回來並對我的函數組合應用一些約束,但由於其懶惰,我很快就遇到了問題。

首先,我寫了這個概念:

template <typename F, typename Ret, typename... Args> 
concept bool Function() 
{ 
    return requires(F f, Args ...args) { 
    { f(args...) } -> Ret; 
    } 
} 

其中我公司以關中Andrew Sutton's origin GitHub的項目中找到的概念。

所以我試圖申請我原來的功能。我的問題是,我不知道什麼G返回不知道什麼參數傳遞給G,所以我不能約束G,我不知道什麼F返回時不知道它給出了什麼參數,我不知道,因爲我不知道什麼G返回。

我很確定我需要一個不關心返回類型的新的Function概念,因爲我的組合函數並不在乎F返回什麼,只要它是可調用的。我想我可以把約束放在內部lambda上,參數類型和正確的G,因此對於F,但這意味着我可以編寫非可組合函數,直到調用站點不會出錯。這是可以避免的嗎?

也許是這樣的:

template <typename F, typename G> 
auto operator*(F f, G g) 
{ 
    return [f, g](auto... args) 
    // is it even possible to constrain here? 
    requires FunctionAnyReturn<G, decltype(args)...> 
     && FunctionAnyReturn<F, decltype(G(decltype(args)...))> 
    { 
    return f(g(args...)); 
    } 
} 

這是我能做的最好的(如果我甚至可以做到這一點)?

回答

1

正如您發現的那樣,將約束放在正確的位置確實很重要。在你的情況下,必須限制結果的operator(),而不是組合函數本身。你真的不能做得更好,比如說很多函數沒有單一返回類型(例如std::make_tuple)。然而,雖然Concepts-Lite確實接觸了一些lambda表達式,但它並沒有像它們那樣允許requires子句,所以你的嘗試將不起作用。

在大多數情況下,我通常的建議是編寫lambda表達式,這樣得到的operator()自然受到SFINAE的限制。在你的情況下,這意味着避免返回類型扣除:

return [f, g](auto... args) -> decltype(f(g(args...))) 
{ return f(g(args...)); } 

如果您使用例如Clang,everything is peachy。如果使用GCC,您可能會遇到一個錯誤GCC performs some checking too early

另一種方法是用自己的方式'展開'lambda表達式的閉包類型。通過使用戶定義類型,您可以訪問所有的招數,特別是你就可以寫上你要明確的限制:

template<typename F, typename G> 
struct compose_type { 
    F first_composed_function; 
    G second_composed_function; 

    template<typename... Args> 
    constexpr auto operator()(Args... args) 
     // substitute in whichever concepts and traits you're actually using 
     requires 
      Callable<G, Args...> 
      && Callable<F, result_of<G, Args...>> 
    { return first_composed_function(second_composed_function(args...)); } 
}; 

template<typename F, typename G> 
constexpr compose_type<F, G> compose(F f, G g) 
{ return { std::move(f), std::move(g) }; } 

Live On Coliru

+0

感謝啊,這看起來像解決方案。這是一個恥辱,因爲我不喜歡這樣一個事實,即我可以在沒有概念錯誤的情況下編寫兩個不可調用的對象。無論如何,我可以提供一個不需要參數類型的回調的Callable變體嗎? –

+1

@ SamKellett不開箱即用。你可以選擇遵守一個協議,在這個協議中,函數對象必須使它們的'簽名'或其他東西接近它,但這是一個巨大的任務,沒有明顯的好處。 –

+0

公平,謝謝你的回答 –

相關問題