C ++ 20 unterwegs! Treffen in Rapperswil-Yona

Published on July 05, 2018

C ++ 20 unterwegs! Treffen in Rapperswil-Yona

    Anfang Juni endete in Rapperswil-Yona das Treffen der internationalen Arbeitsgruppe WG21 zur C ++ - Standardisierung.

    Hier ist, was Sie unter dem Schnitt erwartet:
    • Verträge und Freunde
    • Konzepte (ohne Freunde)
    • __has_cpp_attribute (unwahrscheinlich)
    • bit_cast <my_stuff> (some_array)
    • enthält , shift_left , shift_right , ispow2 , ceil2 ... und die alten Algorithmen unter der neuen Soße
    • atomic_ref
    • Was Neues kann in den Vorlagen geschrieben werden und wie nützlich ist es
    • constexpr virtual foo ()
    • Parallelität 2, Reflexion und Executors TS

    Es wird auch einen Bonus geben: einen Mini-Bereich für Experten:

    • Vom Benutzer deklarierter virtueller Destruktor hat keinen Einfluss auf die Typtrivialität
    • Wo können Sie ein Ausrufezeichen schieben und wie kann es nützlich sein
    • constexpr std :: regex mail_regex (R "((?: (?: [^ <> () \ [\].;: \ s @ \"] + (?: \. [^ <> () \ [ \].,;: \ s @ \ "] +) *) | \". + \ ") @ (?: (?: [^ <> () \ [\].;: \ s @ \" ] + \.) + [^ <> () \ [\].;: \ s @ \ "] {2,}))")


    Verträge


    In C ++ haben 20 Verträge akzeptiert. So können Sie schnell vergessen, Makros für Asserts zu verwenden, sofort die beste Dokumentation zu erhalten und sogar den Leistungsgewinn zu bemerken. Verträge in der Praxis sehen so aus:

    std::string get_name_by_login(std::string_view login)
        [[expects: !login.empty() ]]
        [[ensures ret_value: !ret_value.empty() ]]
    ;
    

    In der obigen Beschreibung ist alles in doppelten Klammern ein Vertrag. Also von ihnen:

    • Der Compiler (abhängig von den Kompilierungsflags) generiert Assertions.
    • Der Compiler (abhängig von den Compiler-Flags) kann daraus Rückschlüsse ziehen und den Code darauf basierend optimieren. Zum Beispiel wird es möglich sein, die Prüfung auf die Ungültigkeit des Rückgabewerts im Benutzercode vollständig zu entfernen.
    • Ein Dienstprogramm eines Drittanbieters (z. B. Doxygen) kann eine umfassendere Dokumentation der Funktion erstellen, ohne dass explizit Bedingungen vor dem Posten / Posten festgelegt werden müssen.
    • Statische Analysatoren können den Code analysieren und Situationen finden, in denen die Verletzung der Vorbedingung garantiert ist (ja, ohne Laufzeit-Tests).

    Vertragsdetails sind in amtlicher Schrift erhältlich .

    Die Bibliothek von FG21 und Fails hat es eilig, den Verträgen zu helfen , was das Speichern und Drucken der Glasspur ermöglicht. Sie können also den Handler des fehlgeschlagenen Vertrags festlegen und eine Folge von Aufrufen ausgeben, die zu dem Problem geführt haben:

    void(const std::contract_violation & e) noexcept {
        std::cerr << "Contract violated in function " << e.function_name() << '\n'
            << std::stacktrace();
    }
    


    Im Falle einer Vertragsverletzung sehen wir eine ähnliche Meldung:

    Contract violated in function std::array<T, N>::operator[](size_type) [with T = int; long unsigned int N = 5ul; ]': 
     0# std::array<int, 5ul>::operator[](unsigned long) at /usr/include/c++/array:124
     1# bar(int) at ../example/assert_handler.cpp:17
     2# foo(int) at ../example/assert_handler.cpp:25
     3# main at ../example/assert_handler.cpp:54
     4# 0x00007F991FD69F45 in /lib/x86_64-linux-gnu/libc.so.6
     5# 0x0000000000401139
    }
    

    std :: stacktrace wurde noch nicht in C ++ 20 übernommen, hat jedoch eine Entwurfsprüfung in der LEWG-Gruppe bestanden, und es wird nur daran gearbeitet, die Beschreibung mit den Regeln des Standards in der LWG-Gruppe in Einklang zu bringen. Die neueste Spezifikation ist als Referenz verfügbar .

    Konzepte


    Konzepte als Sprachfeature wurden bereits in der letzten Sitzung des Komitees aufgenommen , und diesmal wurden die Hauptkonzepte aus Ranges TS in die Standardbibliothek aufgenommen. Jetzt können Sie mit vorgefertigten Konzepten die Anforderungen an die Kompilierungszeit für Vorlagenparameter beschreiben und müssen sich nicht mehr darum kümmern, Ihre eigenen zu schreiben:

    template <class F>
        requires Invocable<F>
    void my_executor::execute(F f) noexcept {
        lock_guard l{data_mutex_};
        push(std::move(f));
    }
    

    Eine vollständige Liste der Konzepte und ihrer Beschreibungen finden Sie in diesem Vorschlag .

    Funktionstest-Makros


    Compiler und Standardbibliotheken stellen seit langem Makros zur Beschreibung der Funktionen des Compilers zur Verfügung. Jetzt sind diese Makros offiziell Teil des Standards und wurden sogar erweitert. So ist es möglich, die Unterstützung des unwahrscheinlichen Attributs durch den Compiler mit dem Ausdruck __has_cpp_attribute (unwahrscheinlich) zu überprüfen .

    Eine Liste der bei der Besprechung aufgenommenen Makros ist in Papierform erhältlich .

    bit_cast


    Die Verwendung von reinterpret_cast zum Konvertieren eines trivial kopierten Typs in einen anderen ist eine sehr schlechte Idee. Aber die Versuchung ist groß und viele Entwickler tun es. Daher hat das C ++ - Komitee speziell für solche Fälle eine Funktion eingerichtet.

    Einfach zu bedienen, einfach austauschen

     my_type my = reinterpret_cast<my_type&>(some_array); 
    auf
    my_type my = std::bit_cast<my_type>(some_array); 

    Wenn nun die Dimensionen von some_array und my_type nicht übereinstimmen oder wenn Sie versuchen, nichttrivial kopierte Typen zu konvertieren, wird beim Kompilieren eine Fehlermeldung angezeigt. In anderen Fällen vermeiden Sie alle fürchterlichen Fehler, die mit dem Typaliasing verbunden sind.

    Algorithmen und neue Funktionen


    Hier sind einige nützliche Dinge, die der Standard-C ++ 20-Bibliothek hinzugefügt wurden:

    • shift_left (it_begin, it_end, unsigned n) - verschiebt Werte im Bereich um n nach links, als ob sie * it_begin = std :: move (* (it_begin + n)), * (it_begin + 1) = std :: move ( * (it_begin + n + 1)) ...
    • shift_right (it_begin, it_end, unsigned n) ähnelt dem obigen Algorithmus, verschiebt jedoch den Bereich nach rechts, als hätten sie * (it_begin + n) = std :: move (* it_begin), * (it_begin + n + 1) = std aufgerufen: : move (* (it_begin + 1)) ...
    • ispow2 (x) - Gibt true zurück, wenn eine Zahl übergeben wird, die eine Zweierpotenz ist
    • ceil2 (x) - rundet eine Zahl auf die nächste Zahl auf, die eine Zweierpotenz ist
    • enthält - alle assoziativen Container, die von der Funktion bool enthält (const key & v) , die true zurückgibt , wenn der Schlüssel im Container enthalten ist

    Darüber hinaus sind vorhandene Algorithmen, die std :: swap und std :: swap selbst verwenden , zu constexpr geworden . Sie können also jetzt sortieren, std :: nth_element aufrufen und die Ergebnisse in der Kompilierungsphase abrufen . Alle Details sind in Papierform bei WG21 erhältlich.

    atomic_ref


    Sie arbeiten mit Datenstrukturen hauptsächlich aus einem Stream, müssen aber irgendwann atomar mit dem Feld arbeiten? In diesen Fällen wurde die Vorlagenklasse atomic_ref <T> hinzugefügt (eine formale Beschreibung ist in Papierform verfügbar ).

    Es ist zu beachten, dass die Atomizität nur für Operationen garantiert wird, die über Instanzen der atomic_ref <T> -Klassen ausgeführt werden . Wenn Sie also in eine Variable ohne atomic_ref schreiben und einen anderen Stream durch atomic_ref einlesen , wird am Ende nichts Gutes herauskommen.

    Übrigens, wenn Sie keine inkonsistenten Typnamen (string _view , atomic _ref ) mögen , drücken Sie Ihre Unzufriedenheit über den Link aus. Wenn es genügend Stimmen gibt, werden wir versuchen, alle Namen in eine Form zu bringen.

    Klasseninstanzen als Vorlagenparameter


    Wenn Sie Klasse X geschrieben haben und einen Operator <=> mit der folgenden Form dafür geschrieben haben:

    struct X {
        // ...
        std::strong_equality operator<=>(const X&, const X&) = default;
        // ...
    };
    

    Dann kann Ihre Klasse als Vorlagenparameter verwendet werden:

    template <X x>
    struct x_as_template_param {
        // ...
    };
    

    Das Komitee hat jedoch noch viel Arbeit, um die Standardbibliothek zu modernisieren und ihre verschiedenen Teile an den Operator zu übertragen .

    constexpr virtuell


    Die Einschränkungen für Funktionen, die zur Kompilierungszeit ausgeführt wurden, wurden gelockert. Jetzt können Sie Klassen mit virtuellen constexpr-Funktionen schreiben.

    Außerdem, wenn Sie in der Basisklasse die Methode constexpr virtual int foo () haben; kann der Nachfolger int foo () entweder constexpr oder nicht sein. Wenn Sie zur Kompilierungszeit foo () aufrufen , lässt der Compiler Sie das Programm nicht kompilieren, wenn Sie versuchen, eine virtuelle Nicht-Constexpr-Funktion auszuführen.

    Diese Änderung öffnet zum Beispiel die Tür zur Implementierung von std :: type_info , das zur Kompilierungszeit gültig ist, wodurch diese Klasse die Boost.TypeIndex- Funktionalität erhalten kann ., mit der Möglichkeit der garantierten Kompilierzeit mit Typen:

    template <class T, class U>
    constexpr bool is_same() {
        constexpr bool res = (typeid(T) == typeid(U));
        return res;
    }
    

    Die neuesten Informationen sind in Papierform verfügbar .

    Parallelität 2, Reflexion und Executors TS


    Parallelism 2 bereitet sich noch auf die Veröffentlichung vor, Details und coole Features können im letzten Beitrag eingesehen werden . Seit seiner Veröffentlichung wurden die mit simd verbundenen Typmerkmale und die für Vektoranweisungen hinzugefügte Funktionalität leicht korrigiert.

    Die Reflexion wird für die Freigabe als technische Spezifikation (TS) vorbereitet. Darin erfolgt die Reflexion immer noch über Mechanismen, die <type_traits> ähneln . Plant, mehr Funktionalität für constexpr-Computing auf den Kern der Sprache zu bringen und die Reflexion über constexpr zu wiederholen! Funktionen (siehe unten).

    Executors in C ++ 20 werden wahrscheinlich nicht fallen, werden aber als separate TS veröffentlicht. Es ist möglich, dass ihr gesamtes Design überarbeitet und vereinfacht wird.

    Vom Benutzer deklarierter virtueller Destruktor und Typtrivialität


    Die Änderung in C ++ 20 (bei der nächsten Besprechung) wirkt sich auf die Leistung verschiedener Teile der Standardbibliothek und der Compileroptimierung aus, ist jedoch in der täglichen Entwicklung wahrscheinlich nicht zu bemerken:

    struct i_am_trivial {
        int foo;
        char bar;
        virtual ~i_am_trivial() = default;
    };
    

    Die Idee ist, dass wenn der Destruktor virtuell ist, dies nicht bedeutet, dass es nicht trivial ist. Somit kann die Standardbibliothek verstehen, dass der Typ, mit dem sie arbeitet, obwohl sie einen virtuellen Destruktor hat, nicht wirklich den Destruktor aufruft, um Ressourcen freizugeben. Dies kann z. B. für std :: vector <Base> zu einer Leistungssteigerung führen , wenn Base über einen solchen virtuellen Destruktor verfügt.

    constexpr!


    Eine weitere interessante Änderung, die für den Empfang in C ++ 20 in Betracht gezogen wird, ist constexpr! funktionen.

    Solche Funktionen dürfen nur in der Übersetzungsphase ausgeführt werden, da jeder Versuch, sie zur Laufzeit zu verwenden, zu einem Übersetzungsfehler führt. Dies ist eine der Änderungen, die für die Reflektion in C ++ erforderlich sind.

    Zusätzlicher Bonus constexpr! Funktionen ist ihre effiziente Nutzung von Compiler-Ressourcen. Weil constexpr!Funktionen werden nicht zur Laufzeit ausgeführt, der Compiler sollte für sie keine Zwischendarstellung (Maschinenbefehle) erzeugen und ihren Speicher nicht behalten oder optimieren. Dadurch wird der vom Compiler benötigte Arbeitsspeicher drastisch reduziert und die Kompilierung etwas beschleunigt. Es sollte sich spürbar auf moderne Bibliotheken auswirken, die stark auf Metaprogrammierung angewiesen sind, wie Boost.Hana oder [Boost.] PFR .

    Anstelle von Summen: constexpr std :: regex


    Viele Programmiersprachen kompilieren / übersetzen derzeit reguläre Ausdrücke, bevor das Programm startet. Wenn das Programm gestartet wird, werden alle regulären Ausdrücke bereits in eine optimierte Zustandsmaschine konvertiert.

    In C ++ ist dies nicht der Fall:

    bool is_valid_mail(std::string_view mail) {
        static const std::regex mail_regex(R"((?:(?:[^<>()\[\].,;:\s@\"]+(?:\.[^<>()\[\].,;:\s@\"]+)*)|\".+\")@(?:(?:[^<>()\[\].,;:\s@\"]+\.)+[^<>()\[\].,;:\s@\"]{2,}))");
        return std::regex_match(
            std::cbegin(mail),
            std::cend(mail),
            mail_regex
        );
    }
    

    Im obigen Code wird die Zustandsmaschine aus dem regulären Ausdruck beim ersten Aufruf der Funktion is_valid_mail () erstellt . Dies ist eine lange Operation, die zusätzlich im kritischen Abschnitt durchgeführt wird.

    Mit den kommenden Neuerungen für constexpr- Berechnungen (constexpr new, is_constexpr_evaluated () usw.) wird es möglich sein, in der Kompilierungsphase viele Dinge in C ++ zu tun, einschließlich constexpr std :: regex .

    Mit constexpr std :: regex wird die Zustandsmaschine für die Funktion is_valid_mail () in der Kompilierungsphase erstellt. Darüber hinaus kann GCC optimierte reguläre Saison bei der Kompilierung ohne erzeugt statische const , weil ausgehend von gcc-6 wenn constexprdie Funktionen alle Parameter an der Eingabe sind Konstanten, GCC erzwingt die Berechnung in der Kompilierungsphase.

    Also, wie gefällt Ihnen die Idee constexpr der std :: regex ?

    PS: Für diejenigen, die Geld wollen , war C ++ kürzlich das Yandex.Taxi Coding Fest . Es wird möglich sein, Rivalen mit C ++ 17 zu bewaffnen und zu besiegen.