2

だから私は GCC6 とその概念の実装で遊んでいて、Haskell Prelude が実験の良いソースになると考えました。Haskell のコア機能の 1 つは関数合成であり、これは私がすぐに取り組む必要があったものです。

できる限り 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 の origin github プロジェクトにある概念に基づいています。

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

呼び出し可能である限りFunction、構成関数は戻り値を気にしないため、戻り値の型を気にしない新しい概念が必要であると確信しています。Fそして、パラメーターの型と 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...));
  }
}

これは私ができる最善のことですか (それができる場合でも)?

4

1 に答える 1

1

お気づきのように、制約を適切な場所に置くことは確かに重要です。あなたの場合、それはoperator()合成関数自体ではなく、制約されなければならない結果です。たとえば、多くの関数が単一の戻り値の型を持たないことを考慮してください (例: std::make_tuple)。ただし、Concepts-Lite はラムダ式に少し触れていますが、ラムダ式のrequires句を許可するところまでは行っていないため、この試みはうまくいきません。

ほとんどの場合、私の通常のアドバイスは、operator()SFINAE のおかげで結果が自然に制約されるようにラムダ式を記述することです。あなたの場合、これは戻り型の推論を回避することを意味します:

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

たとえば Clang を使用している場合、すべてが peachyです。GCC を使用している場合、GCC のチェックが早すぎるというバグに遭遇する可能性があります。

別の方法は、ラムダ式のクロージャ タイプを「展開」することです。ユーザー定義型にすることで、すべてのトリックにアクセスできるようになり、特に、必要な明示的な制約を記述できます。

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

于 2016-04-10T19:41:46.893 に答える