2016-04-09 3 views
2

Also spiele ich mit GCC6 und dessen Konzeptimplementierung und ich dachte, das Haskell Prelude wäre eine gute Quelle für Experimente. Eines der Kernmerkmale von Haskell ist die Funktionszusammensetzung, und das musste ich sofort angehen.Wie beschränke ich eine faule Komposition, bevor ich die aufrufbaren Argumente kenne?

Imitieren die Haskell Syntax wie am besten, dass ich konnte, ich diese Funktion schrieb:

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

die großen Werke und ermöglicht es mir, Dinge zu tun wie:

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

Glücklich, habe ich beschlossen, gehen Zurück und wenden Sie einige Einschränkungen für meine Funktion Zusammensetzung, aber ich habe schnell ein Problem aufgrund seiner Faulheit.

Zuerst schrieb ich dieses Konzept:

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

Was ich der gefundenen Begriffe in Andrew Sutton's origin Github-Projekt basiert.

Und so habe ich versucht, auf meine ursprüngliche Funktion anzuwenden. Das Problem, das ich habe, ist, dass ich nicht weiß, was G zurückkehrt, ohne zu wissen, welche Argumente an G übergeben werden, so kann ich G nicht beschränken und ich weiß nicht, was F zurückgibt, ohne zu wissen, welcher Parameter es gegeben wird und ich nicht weiß das, weil ich nicht weiß, was G zurückgibt.

Ich bin mir ziemlich sicher, dass ich ein neues Function Konzept benötigen, die nicht über den Rückgabetyp als meine Komposition Funktion kümmert sich nicht darum, was F kehrt scheren, so lange, wie es aufrufbare ist. Und ich denke, ich könnte die Einschränkung auf das innere Lambda setzen, das der Parameter für G und damit für F gibt und korrigiert, aber das bedeutet, dass ich nicht zusammensetzbare Funktionen schreiben kann und bis zur Aufrufstelle keinen Fehler bekommen werde. Ist das vermeidbar?

Vielleicht so etwas wie:

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

Ist dies das Beste, was ich tun kann (wenn ich auch das tun können)?

Antwort

1

Wie Sie herausgefunden haben, ist es in der Tat wichtig, Einschränkungen an der richtigen Stelle zu platzieren. In Ihrem Fall muss die operator() des Ergebnisses eingeschränkt werden, nicht die Kompositionsfunktion selbst. Sie können es wirklich nicht besser machen, zum Beispiel, dass viele Funktionen keinen einzigen Rückgabetyp haben (z. B. std::make_tuple). Aber während Concepts-Lite Lambda-Ausdrücke ein wenig berührt, geht es nicht so weit, eine Klausel requires auf ihnen zuzulassen, also wird Ihr Versuch nicht funktionieren.

In den meisten Situationen ist mein üblicher Ratschlag, den Lambda-Ausdruck so zu schreiben, dass der resultierende operator() dank SFINAE natürlich eingeschränkt ist. In Ihrem Fall bedeutet dies die Vermeidung von Rückgabeguthaben:

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

Wenn Sie z. Clang, everything is peachy. Wenn Sie GCC verwenden, können Sie auf einen Fehler stoßen, bei dem GCC performs some checking too early.

Eine Alternative besteht darin, den Verschlusstyp des Lambda-Ausdrucks zu "entrollen".Indem sie einen benutzerdefinierten Typ, erhalten Sie Zugang zu allen Tricks und insbesondere kann man dann schreiben die expliziten Einschränkungen, die Sie wollen:

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

Dank ja das sieht aus wie die Lösung. Es ist eine Schande, weil ich die Tatsache nicht mag, dass ich zwei nicht aufrufbare Objekte ohne einen Konzeptfehler zusammensetzen kann. Gibt es da überhaupt eine Callable-Variante des Callbacks, bei der die Argumenttypen nicht vorab benötigt werden? –

+1

@SamKellett Nicht out of the box. Sie können sich dafür entscheiden, ein Protokoll einzuhalten, bei dem Funktionsobjekte ihre "Signatur" oder etwas, das diesem nahe steht, zur Verfügung stellen, aber es ist ein großes Unterfangen ohne offensichtlichen Nutzen. –

+0

fair, danke für die Antwort –

Verwandte Themen