[Gelöst] Spezialisierung std::swap



  • a) Köln ist nicht Ruhrgebiet
    b) Ich frag jeden Piraten vorher nach std::swap Spezialisierungen. Der Pirat, der mir was dazu sagen kann, bekommt ein Bier.



  • @DocShoe sagte in [Gelöst] Spezialisierung std::swap:

    a) Köln ist nicht Ruhrgebiet

    Sei mal nicht so kleinlich, immerhin ist die Metropolregion Rhein-Ruhr die einzige deutsche "Megacity" ... die zwei Rübenacker dazwischen und die kommunale Organisationsstruktur machen den Braten nicht wirklich fett 😉



  • @DocShoe sagte in [Gelöst] Spezialisierung std::swap:

    Soooo... Problem gefunden und gelöst. Das Problem war die Klasse RingBuffer, die zwar die Rule of Five implementiert, aber eben nicht richtig, weil der Move-Constructor und Move-Assignment nicht noexcept waren.

    Da bist Du auf die Bugs in der Definition der Standard Library gestoßen von denen ich schon mehrfach geschrieben habe. Die Norm fordert, dass swap und move noexcept sind, weil bestimmte Mikrooptimierungen genutzt werden können sollen. Diese können aber nur dann genutzt werden, wenn der Allocator dies durch propagate_on_container_move_assignment oder durch is_always_equal anzeigt. Sollte das beides nicht erfüllt sein, und Du änderst das trotzdem auf noexcept, hast Du UB ins Programm eingebaut.


  • Mod

    @DocShoe sagte in [Gelöst] Spezialisierung std::swap:

    Das Problem war die Klasse RingBuffer, die zwar die Rule of Five implementiert, aber eben nicht richtig, weil der Move-Constructor und Move-Assignment nicht noexcept waren.

    Nur fuer mein eigenes Verstaendnis: Das Problem war, dass die explizite Spezialisierung fälschlicherweise noexcept markiert war, wo das stdlib Template noexcept(false) markiert haette (weil letzteres ad hoc deduziert wird)?

    @john-0 Das hat eigentlich mehr mit exception safety zu tun, und ueberhaupt sprechen wir hier von swap und nicht von Containern, aber juckt Dich das eigentlich?



  • @Columbo Nein, @john-0 juckt prinzipiell nix, will nur vorbringen was ihm gerade durchs Hirn spukt. Aber ich geh' jetzt nachschauen was template deduction zu noexcept sagt, danke dafür 😘



  • @Columbo sagte in [Gelöst] Spezialisierung std::swap:

    @john-0 Das hat eigentlich mehr mit exception safety zu tun, und ueberhaupt sprechen wir hier von swap und nicht von Containern, aber juckt Dich das eigentlich?

    TLDR
    Wäre die Norm korrekt, hätte DocShoes Code ohne Mikrooptimierungen übersetzt werden müssen.

    @Columbo Man merkt mal wieder, dass Du keine Ahnung hast.

    Das Thema hängt direkt zusammen, weil in der ISO Norm Annahmen gemacht werden, damit Mikrooptimierungen genutzt werden können. D.h. die Norm fordert, dass move und swap noexcept sind und der Aufwand dafür O(1) ist, weil nur so Zeiger auf Daten direkt getauscht werden können. Diese Annahme ist aber nur dann erfüllbar, wenn der verwendete Allocator die entsprechenden Member (IAE, POCS, POCMA) definiert. Alle Algorithmen, Container, Funktionen, … in der Norm setzen dieses Verhalten voraus, obwohl die Norm explizit erlaubt, dass Allocatoren sich so nicht verhalten müssen.

    Korrekt wäre es, wenn die Norm die Mikrooptimierungen und Exception Safety in der Standard Library nicht voraussetzen würde, sondern nur dann, wenn man durch die entsprechenden Meta Programming Prädikate für einen durch den Nutzer definierten Typen anzeigen. DocShoe führte doch die Prädikate
    is_move_constructible, is_move_assignable, is_nothrow_move_constructible oder is_nothrow_move_assignable an. Wenn ein Allocator weder (IAE, POCS, POCMA) definiert kann weder is_nothrow_move_constructible noch is_nothrow_move_assignable erfüllt sein, weil dann immer die Möglichkeit besteht, dass alloziert und kopiert wird und somit kann das durch den Nutzer definierte swap bzw. move auch nicht noexcept sein. Oder anders formuliert wozu gibt es all die schönen Prädikate, wenn die Library sie selbst nicht nutzt?



  • @Columbo siehst?



  • @Columbo sagte in [Gelöst] Spezialisierung std::swap:

    @DocShoe sagte in [Gelöst] Spezialisierung std::swap:

    Das Problem war die Klasse RingBuffer, die zwar die Rule of Five implementiert, aber eben nicht richtig, weil der Move-Constructor und Move-Assignment nicht noexcept waren.

    Nur fuer mein eigenes Verstaendnis: Das Problem war, dass die explizite Spezialisierung fälschlicherweise noexcept markiert war, wo das stdlib Template noexcept(false) markiert haette (weil letzteres ad hoc deduziert wird)?

    Das habe ich auch so verstanden und halte es für keine gute Lösung wenn hier - aus welchem Grund auch immer - der selbe noexcept-Wert wie für die Standard-Implementierung forciert wird, da eine eigene swap-Implementierung theoretisch überhaupt kein move verwenden muss und in diesem Fall nicht-relevante Constraints geprüft werden.

    Das noexcept ist doch nicht Teil der Funktions-Signatur. Sollte die Spezialisierung nicht eigentlich auch dann möglich sein, wenn deren noexcept abweicht, oder verstehe ich da etwas falsch?

    Immerhin ist so aufgefallen, dass da bei Konstruktoren/Assignment noch was im argen lag, hat auch was.

    @john-0 Ist das nicht alles überhaupt nur dann relevant, wenn die eigene Klasse wie z.B. der RingBuffer tatsächlich beliebige Objekte verwaltet und überhaupt Allokatoren unterstützt? Ich sehe hier keinen Hinweis darauf. Geschweige denn wie swap überhaupt implemenmtiert ist.


  • Mod

    @Swordfish Das Wort 'deduzieren' ist halt leider etwas ueberladen.... 😉

    @john-0 sagte in [Gelöst] Spezialisierung std::swap:

    D.h. die Norm fordert, dass move und swap noexcept sind und der Aufwand dafür O(1) ist, weil nur so Zeiger auf Daten direkt getauscht werden können.

    Warum kann ich nicht moven nur weil der move nicht noexcept ist? Dir ist klar, dass swap nicht pauschal noexcept ist, sondern abhaengig der noexcept Markierung der erforderlichen Funktionen?

    @Finnegan sagte in [Gelöst] Spezialisierung std::swap:

    Das noexcept ist doch nicht Teil der Funktions-Signatur. Sollte die Spezialisierung nicht eigentlich auch dann möglich sein, wenn deren noexcept abweicht, oder verstehe ich da etwas falsch?

    noexcept ist seit C++17 Teil des Funktionstyps. Eine explizite Spezialisierung ist quasi eine Re-Deklaration der Spezialisierung und muss damit mit dem Typ uebereinstimmen, den das Template vorgibt.



  • @Columbo sagte in [Gelöst] Spezialisierung std::swap:

    noexcept ist seit C++17 Teil des Funktionstyps. Eine explizite Spezialisierung ist quasi eine Re-Deklaration der Spezialisierung und muss damit mit dem Typ uebereinstimmen, den das Template vorgibt.

    Verstehe. Ungünstig allerdings, dass

    noexcept(
        std::is_nothrow_move_constructible<T>::value &&
        std::is_nothrow_move_assignable<T>::value
    )
    

    zwar für die Default-Implementierung Sinn macht, aber eben nicht zwingend für eine eigene Implementierung , die sogar ganz andere Anforderungen haben könnte, damit sie noexcept ist. So hat der Code, mit dem eine Funktionalität implementiert wird, indirekt Auswirkungen auf die Signatur, wenn man das konsequent so durchzieht. Das halte ich für keine so gute Idee.

    Vielleicht bekommen wir ja irgendwann noch sowas wie ein noexcept(auto) (sogar als Default), das immer genau dann true ist, wenn die Funktion nicht wirft und keine noexcept(false)-Funktionen aufruft. Das wär echt praktisch 😉


Anmelden zum Antworten