C++17 hat Concepts



  • PushButton schrieb:

    Ich bin kein C++ Experte, was sind Concepts?

    Vereinfacht ausgedrückt:

    template <typename T>
    const T& min(T &a, T &b) {...}
    

    Verlangt, dass T einen entsprechenden operator< hat und führt zu umständlichen Fehlermeldungen, wenn ers nicht hat.
    Mit concepts:

    template<typename T>
    concept bool LessThanComparable()
    {
        return requires (T a, T b) {a < b};
    }
    
    const LessThanComparable& min(LessThanComparable &a, LessThanComparable &b) {...}
    

    Hab die aktuelle Syntax jetzt nicht im Kopf, aber sie ermöglichen halt bessere Templates und vermeiden viele SFINAE Tricks.
    Und aufrgrund dieser extrem kurzen Syntax, nennt man in C++ Typen auch nicht im CamelCase. :p



  • Lässt sich im Prinzip mit interfaces vergleichen, wie man sie aus Java oder php kennt. Einfach ein Werkzeug, um dem Compiler zu sagen, welche Eigenschaft ein Object haben muss.



  • Bengo schrieb:

    Lässt sich im Prinzip mit interfaces vergleichen, wie man sie aus Java oder php kennt. Einfach ein Werkzeug, um dem Compiler zu sagen, welche Eigenschaft ein Object haben muss.

    Concepts sind so ziemlich genau das Gegenteil. Concepts beruhen auf Compile-Time-Polymorphie, während Java und die Personal Home Page Run-Time-Polymorphie machen ...

    @Nathan: Ja, so ungefähr, auch wenn die Syntax mittlerweile ein bisschen kompakter ist.



  • Wenn man "concept" nicht übersetzt zu "Konzept", sondern zu "Begriff", dann hat man schon gewonnen.



  • Ich freu mich schon drauf sie auszuprobieren und in den Workflow zu integrieren.
    Ist anfänglich erstmal ein bisschen wie ein neues Spielzeug :).



  • conzepts werden vor allem die Compiler Fehlermeldungen verbessern wenn man einer generischen Funktion die gewisse Anforderungen an die verwendeten Typen stellt Typen Übergibt die diese Anforderungen nicht erfüllen.
    Heute bekommt man die Art von Fehlermeldungen die viele jammern lassen, und ein Compiler bringt den ganzen Stack von oben nach unten, der andere von unten nach oben, ...

    Was mir gefällt, an dem was ich gesehen habe, ist das Funktionen sich fast selber beschreiben. Man sieht dei Deklaration und weis was erwartet wird.
    Stroustrup hatte bei einem Talk den ich auf youtube gesehen habe einige nette Beispiele dabei. Die sahen ein bisschen anders (besser für meinen Geschmack) als die Beispiele im wikipedia, aber mal sehen wie es letztendlich wird



  • Danke für die Erklärung, dann kann ich die Begeisterung über dieses neue Feature durchaus verstehen. Ich habe zwar noch nie was mit Templates gemacht, habe aber schon von der schwierigen Fehlersuche durch kryptische Fehlertexte gelesen.



  • Mal eine kurze Frage.
    Kann ich dem Concept sagen,dass type T eine function haben muss, mit einer entsprechenden Signatur oder ist das Ganze beschränkt, wie im obigen Beispiel
    auf Operatoren?


  • Mod

    @volkard: 😃 😃 😃
    https://www.c-plusplus.net/forum/p2158260#2158260

    volkard schrieb:

    Nexus schrieb:

    krümelkacker schrieb:

    und dann ist auto ja auch nur ein Typ-Platzhalter, der entsprechend dekoriert werden kann:

    vector<auto> blah = myfunction();
    

    Ah, das kannte ich ebenfalls nicht. Interessant! 💡

    Naja, gcc kennt das auch nicht.
    Ist es wirklich erlaubt?

    Ja.

    Nathan schrieb:

    Hab die aktuelle Syntax jetzt nicht im Kopf

    So:

    template<typename T>
    concept bool LessThanComparable = requires (T a, T b) {{a < b} -> bool;}
    

    Kann ich dem Concept sagen,dass type T eine function haben muss, mit einer entsprechenden Signatur oder ist das Ganze beschränkt, wie im obigen Beispiel
    auf Operatoren?

    template <typename T>
    concept bool C = requires (T a, int i, float b) {
        {a.function(i, b)} -> std::string;
    };
    


  • Arcoth schrieb:

    Kann ich dem Concept sagen,dass type T eine function haben muss, mit einer entsprechenden Signatur oder ist das Ganze beschränkt, wie im obigen Beispiel
    auf Operatoren?

    template <typename T>
    concept bool C = requires (T a, int i, float b) {
        {a.function(i, b)} -> std::string;
    };
    

    Wobei das jetzt nicht ganz genau die Signatur prüft. Die Memberfunktion könnte auch als std::string function(float i, float b) deklariert worden denke ich. Schließlich prüft requires nur ob der Ausdruck wohlgeformt ist, aber die Konvertierung int -> float kann implizit passieren (wie noch viele weitere Konvertierungen).


  • Mod

    sebi707 schrieb:

    Wobei das jetzt nicht ganz genau die Signatur prüft.

    Das ist mir klar. Es macht nur einfach nie Sinn die genaue Signatur einer Methode zu prüfen.


  • Mod

    Na gut, man kann natürlich ganz rustikal anpacken:

    template <typename T>
    concept bool HasF = requires {
    	{&T::f} -> void (T::*)(int);
    };
    

    Ist aber wie gesagt nicht sonderlich nützlich, weil man ja i.d.R. die Funktion aufrufen und nicht per Funktionszeiger irgendwo speichern will.



  • Arcoth schrieb:

    Na gut, man kann natürlich ganz rustikal anpacken [...]

    Ah nett. Darauf bin ich jetzt gerade nicht gekommen...



  • Ich hab mir mal den gcc trunk compiliert und etwas mit concepts rumgespielt und bin auf etwas merkwürdiges gestoßen:

    int foo(int x)
    {
            return 0;
    }
    
    template <typename T>
    concept bool C = requires (T x) {
            {foo(x)} -> int&;
    };
    
    void bar(C x)
    {
            //int& y = foo(x);
    }
    
    int main()
    {
            bar(5);
    }
    

    Ich hätte hier erwartet, dass mein Concept C nicht erfüllt ist weil foo keine Referenz zurück gibt. Stattdessen gibts dann erst einen Fehler wenn ich die Zuweisung int& y = foo(x); auskommentiere. Habe ich etwas falsch verstanden oder wieso funktioniert das nicht?

    14.10.1.5 Implicit conversion constraints schrieb:

    An implicit conversion constraint is a constraint that specifies a requirement on the implicit conversion of an expression E to a type T.


  • Mod

    Ja, das ist ein Bug. Reporte mal.


  • Mod

    Huch. Ich glaub' GCC hat nicht mehr alle Tassen im Schrank:

    int foo(int x) 
    { 
            return x; 
    } 
    
    template <typename T> 
    concept bool C = requires (T x) { 
            {foo(x)} -> void; 
    }; 
    
    static_assert( C<int> );
    

    Das kompiliert gar. Merkwürdig. Dabei ist gar nichts implizit zu void konvertierbar..



  • Arcoth schrieb:

    Reporte mal.

    Erledigt: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67240



  • Arcoth schrieb:

    template<typename T>
    concept bool LessThanComparable = requires (T a, T b) {{a < b} -> bool;}
    

    Mal ne dumme Frage, die Syntax war doch mal etwa so (wenn ich mich richtig erinnere)

    template <typename T>
    concept bool LessThanComparable
    {
        bool operator<(T a, T b); // T braucht einen operator< der 2 T's nimmt und bool zurückgibt
    }
    

    Wieso ist das jetzt so "häßlich" mit =requires und Pfeil für den Rückgabewert? Das concept Schlüsselwort sollte doch ausreichen um dem Compiler zu sagen dass jetzt die requirements für T kommen, gibts einen bestimmten Grund warum man genau die Syntax gewählt hat?



  • happystudent schrieb:

    Wieso ist das jetzt so "häßlich" mit =requires und Pfeil für den Rückgabewert? Das concept Schlüsselwort sollte doch ausreichen um dem Compiler zu sagen dass jetzt die requirements für T kommen, gibts einen bestimmten Grund warum man genau die Syntax gewählt hat?

    Die neue Version ist allgemeiner. Das alte sagt lediglich, dass der operator < (a, b) vorhanden ist und bool zurueckgibt. Das neue hingegen sagt, dass diue expression a<b gueltig ist und das Ergebnis in boolean umgewandelt werden kann, quasi dass

    bool c = a<b
    

    gueltig ist.
    Mehrere requires koennen wahrscheinlich auch mit logischen operatoren verbunden werden.

    Ausserdem sieht es vermutlich besser aus, wenn man nicht alles in einer Zeile schreibt:

    template<typename T>
    concept bool LessThanComparable =
    requires (T a, T b) {
        {a < b} -> bool;
    }
    


  • Marthog schrieb:

    Die neue Version ist allgemeiner. Das alte sagt lediglich, dass der operator < (a, b) vorhanden ist und bool zurueckgibt. Das neue hingegen sagt, dass diue expression a<b gueltig ist und das Ergebnis in boolean umgewandelt werden kann, quasi dass

    bool c = a<b
    

    gueltig ist.

    Ok, also würde das concept sowohl für eine freie operator-Funktion bool operator<(T a, T b) als auch für eine Member-operator-funktion bool T::operator<(T b) funktionieren? Das ist dann natürlich ein Vorteil der Schreibweise 👍


Anmelden zum Antworten