2016-04-09 3 views
2

私はGCC6​​とそのコンセプトの実装を試しています。私はHaskell Preludeが実験のための良いソースであると考えました。ハスケルの中心的な機能の1つは関数の構成であり、これは私が直ちに取り組む必要があったものです。呼び出し可能な引数を知る前に、怠惰なコンポジションを制約するにはどうすればよいですか?

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

素晴らしい作品と私​​は同じようなものを行うことができます:私は、私はこの関数を書いたことが最高のようにHaskellの構文模倣

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); 

幸せは、私が行くことにしました私の機能構成にいくつかの制約を適用しましたが、私はすぐにその怠惰のために問題にぶつかりました。

まず私はこのコンセプトを書いた:私はAndrew Sutton's origin githubのプロジェクトで見つかった概念のオフに基づいて

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

私は元の機能に適用しようとしました。私が持っている問題は、Gがどんな引数がGに渡されるのかわからずに何が返ってくるのかわからないということです。Gを拘束することができず、何が与えられているのかわからずに何を返すのか分かりません。それは私が何をG返すかわからないので知っている。

は返信の種類に関係なく、Fが返すものを気にしない新しいFunctionというコンセプトが必要であると私は確信しています。そして、私はパラメータがタイプし、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はラムダ式に少し触れていますが、それではrequires節を許可していないので、あなたの試みはうまくいかないでしょう。

ほとんどの場合、私の通常のアドバイスは、結果的に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というバグが発生する可能性があります。

代わりに、ラムダ式のクロージャータイプを 'アンロール'する方法があります。それをユーザー定義型にすることで、あなたはすべてのトリックへのアクセスを獲得し、特にあなたは、あなたがしたい明示的な制約を書き込むことができます。

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

おかげええ、これは解決策のように見えます。私は概念的なエラーなしに2つの非呼び出し可能なオブジェクトを作成できるという事実が気に入らないので、残念です。とにかく、引数型を前もって必要としないCallableのコールバックを提供できるのですか? –

+1

@SamKellett箱のままではありません。関数オブジェクトがその「署名」やそれに近いものを作る必要があるプロトコルを遵守することを選択できますが、それは明らかなメリットがない巨大な取り組みです。 –

+0

公平、答えてくれてありがとう –

関連する問題