[c++20] rewritten operator == und clang



  • Hallo,
    ich habe nun schon ein paar Stunden in das folgende Problem versenkt. Ich möchte gerne über die Operatoren ==, != und <=> gerne ein nicht bool zurück geben. Der Standard deckt dies meines Wissens nach auch ab. Warum dies notwendig ist oder auch nicht, würde ich hier ungerne diskutieren; nur soviel: es entspricht einer komplett anderen Semantik.

    Seit c++20 ist der compiler dazu angehalten, gerade jene operatoren als Basis heranzuziehen und logisch die fehlenden abzuleiten (in meinem Fall macht vorallem das reverse Probleme). Der compiler ist aber scheinbar ebenfalls dazu angehalten, herumzujammern, wenn ein nicht-bool operator reversed werden soll und dieses mit einem compile-error deutlich zu machen, statt einfach keine reversed Variante anzubieten. Das macht es etwas kritisch, da diese potentielle reversed Variante scheinbar mit in die lookup-phase aufgenommen wird und dann sehr schnell als die beste Möglichkeit auserkoren wird.
    Nun, um das zu umgehen, habe ich nun einfach alle unterschiedlichen Überladungen hinzugefügt und gehofft, das es reicht (ich brauche für lvalue-references und rvalue-references dezent anderes Verhalten). Tatsächlich hat dies zum teilweisen Erfolg geführt, dennoch musste ich auch hier schon wieder in die Trickkiste greifen und die Überladungen für myType, anyType, anyType, myType und myType, myType, jeweils als const-lvalue-reference und rvalue-reference Variante, hinzufügen. Diese 8 Varianten sind zwar nicht schön, erfüllen aber ihren Zweck und ich könnte damit leben. Das Problem ist nur, dass von den großen dreien, clang wieder einmal herumzickt und mit einer derben Vehemenz ablehnt. Ich habe dazu mal folgendes Testbed zusammengestellt, so dass ihr euch selbst mal ein Bild davon machen könnt. Das seltsame aus meiner Sicht ist, dass clang unbedingt versucht operatoren zu reversen, obwohl sie in beide Richtungen bereits vorhanden sind. Und bevor jemand auf die Idee kommt, nun auch noch alle lvalue-reference Überladungen auch noch hinzuzufügen: spart euch die Mühe, das hab ich selbst schon versucht. Der Fehler bleibt der gleiche. Und bevor das auch noch vorgeschlagen wird: Auch die const rvalue-reference Überladungen habe ich selbstverständlich schon hinzugefügt (man kann ja nie wissen). Jedoch ohne Erfolg.

    Hat jemand eine Idee, wie ich hier clang beruhigen kann?

    MfG
    Dominic



  • @DNKpp sagte in [c++20] rewritten operator == und clang:

    Hallo,
    ich habe nun schon ein paar Stunden in das folgende Problem versenkt. Ich möchte gerne über die Operatoren ==, != und <=> gerne ein nicht bool zurück geben. Der Standard deckt dies meines Wissens nach auch ab.

    Sicher? Was zur Hölle soll den ein == sonst zurückliefern? Entweder sind die Operanden gleich oder auch nicht.



  • Ich kann mir auch nicht vorstellen, dass das vom Standard abgedeckt ist:

    https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf 7.6.10 Equality operators

    The operators == and != both yield true or false, i.e., a result of type bool


  • Mod

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Ich möchte gerne über die Operatoren ==, != und <=> gerne ein nicht bool zurück geben.

    Das laesst Dich schon mal ein wenig verwirrt aussehen, zumal <=> nie einen bool ergeben sollte (tertium non datur...),

    Ich hab keine Ahnung warum du alles so kompliziert machst (Edit: scheinbar damit lvalue/rvalue Referenzen dezent anderes Verhalten geben? wirst Du erläutern müssen), ich hab in deinem Beispiel mal die meisten Operatorenfunktionen geloescht und es klappt (wie erwartet):

        template <class TRhs>
            requires (!derived_from_unified_base<TRhs, equal_tag>)
        friend constexpr auto operator ==(const equal_operator& lhs, TRhs&& rhs);
        template <class TLhs>
            requires (!derived_from_unified_base<TLhs, equal_tag>)
        friend constexpr auto operator ==(TLhs&& lhs, const equal_operator& rhs);
        template <class UDerived>
        friend constexpr auto operator ==(const equal_operator& lhs, const equal_operator<UDerived>& rhs);
    

    https://godbolt.org/z/vv45YsKET

    Das Problem in deinem zitierten Code war anscheinend, dass fuer i == move(i) der synthetische Kandidat

    template <class UDerived>
    friend constexpr auto operator ==(const equal_operator<UDerived>& rhs, equal_operator&& lhs)
    

    von Clang faelschlicherweise als ein besserer match angesehen wird, als

    template <class UDerived>
    friend constexpr auto operator ==(const equal_operator& lhs, equal_operator<UDerived>&& rhs)
    

    Obwohl wir offensichtlich sehen, dass beide von der ICS her vollkommen identisch sind und auch beide gleich spezialisiert sind. Daher muesste http://eel.is/c++draft/over.match.best#general-2.8 greifen, was garantiert, dass der non-rewritten candidate ceteris paribus bevorzugt wird.

    @Schlangenmensch sagte in [c++20] rewritten operator == und clang:

    Ich kann mir auch nicht vorstellen, dass das vom Standard abgedeckt ist:

    https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf 7.6.10 Equality operators

    The operators == and != both yield true or false, i.e., a result of type bool

    Das gilt fuer built-in operators, nicht ueberladene Operatoren.

    @Tyrdal sagte in [c++20] rewritten operator == und clang:

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Hallo,
    ich habe nun schon ein paar Stunden in das folgende Problem versenkt. Ich möchte gerne über die Operatoren ==, != und <=> gerne ein nicht bool zurück geben. Der Standard deckt dies meines Wissens nach auch ab.

    Sicher? Was zur Hölle soll den ein == sonst zurückliefern? Entweder sind die Operanden gleich oder auch nicht.

    In Bibliotheken die zur Meta-Programmierung gehoeren, bspw. Boost.Proto oder so, kann das Sinn ergeben.


  • Mod

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Nun, um das zu umgehen, habe ich nun einfach alle unterschiedlichen Überladungen hinzugefügt und gehofft, das es reicht (ich brauche für lvalue-references und rvalue-references dezent anderes Verhalten).

    Hast Du nur um das zu umgehen die Überladungen hinzugefügt, oder bräuchtest Du sie ohnehin? Und wie stark variieren die Rueckgabetypen relativ zur value category der Argumente? Wenn nicht, dann kannst Du ja einen generischen operator==(T&&,U&&) definieren, der intern via if constexpr differenziert.



  • Das erste was am Test negativ auffällt ist, dass auch der GCC massenweise „unused parameter“ Meldungen wirft. Da geht das eigentliche Problem im Rauschen schlechten Codes unter.



  • @Columbo sagte in [c++20] rewritten operator == und clang:

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Ich möchte gerne über die Operatoren ==, != und <=> gerne ein nicht bool zurück geben.

    Das laesst Dich schon mal ein wenig verwirrt aussehen, zumal <=> nie einen bool ergeben sollte (tertium non datur...),

    Du hast vollkommen recht. Ich schiebe es mal auf die Uhrzeit, dass ich mich hier etwas unglücklich ausgedrückt habe 😉

    @Columbo sagte in [c++20] rewritten operator == und clang:

    Ich hab keine Ahnung warum du alles so kompliziert machst (Edit: scheinbar damit lvalue/rvalue Referenzen dezent anderes Verhalten geben? wirst Du erläutern müssen), ich hab in deinem Beispiel mal die meisten Operatorenfunktionen geloescht und es klappt (wie erwartet):

        template <class TRhs>
            requires (!derived_from_unified_base<TRhs, equal_tag>)
        friend constexpr auto operator ==(const equal_operator& lhs, TRhs&& rhs);
        template <class TLhs>
            requires (!derived_from_unified_base<TLhs, equal_tag>)
        friend constexpr auto operator ==(TLhs&& lhs, const equal_operator& rhs);
        template <class UDerived>
        friend constexpr auto operator ==(const equal_operator& lhs, const equal_operator<UDerived>& rhs);
    

    https://godbolt.org/z/vv45YsKET

    Es geht im Endeffekt darum, dass ich gerne für rvalue referenzen das Source-Object entmoven möchte, statt zu kopieren und daher auch rvalue-refs statt einer lvalue-ref weiter geben.

    Das Problem in deinem zitierten Code war anscheinend, dass fuer i == move(i) der synthetische Kandidat

    template <class UDerived>
    friend constexpr auto operator ==(const equal_operator<UDerived>& rhs, equal_operator&& lhs)
    

    von Clang faelschlicherweise als ein besserer match angesehen wird, als

    template <class UDerived>
    friend constexpr auto operator ==(const equal_operator& lhs, equal_operator<UDerived>&& rhs)
    

    Obwohl wir offensichtlich sehen, dass beide von der ICS her vollkommen identisch sind und auch beide gleich spezialisiert sind. Daher muesste http://eel.is/c++draft/over.match.best#general-2.8 greifen, was garantiert, dass der non-rewritten candidate ceteris paribus bevorzugt wird.

    Also ein compiler bug? Sollte ich dann wohl reporten.

    @Columbo sagte in [c++20] rewritten operator == und clang:

    @Schlangenmensch sagte in [c++20] rewritten operator == und clang:

    Ich kann mir auch nicht vorstellen, dass das vom Standard abgedeckt ist:

    https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf 7.6.10 Equality operators

    The operators == and != both yield true or false, i.e., a result of type bool

    Das gilt fuer built-in operators, nicht ueberladene Operatoren.

    Genau das. Habe mich bewusst vorher belesen und entsprechenden Passus natürlich auch gefunden.

    @Columbo sagte in [c++20] rewritten operator == und clang:

    @Tyrdal sagte in [c++20] rewritten operator == und clang:

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Hallo,
    ich habe nun schon ein paar Stunden in das folgende Problem versenkt. Ich möchte gerne über die Operatoren ==, != und <=> gerne ein nicht bool zurück geben. Der Standard deckt dies meines Wissens nach auch ab.

    Sicher? Was zur Hölle soll den ein == sonst zurückliefern? Entweder sind die Operanden gleich oder auch nicht.

    In Bibliotheken die zur Meta-Programmierung gehoeren, bspw. Boost.Proto oder so, kann das Sinn ergeben.

    In exakt so einem Kontext bewegen wir uns hier auch. Im Endeffekt versuche ich aus zwei (oder mehr) Prädikaten ein gemeinsames Prädikat zu erstellen.

    @Columbo sagte in [c++20] rewritten operator == und clang:

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Nun, um das zu umgehen, habe ich nun einfach alle unterschiedlichen Überladungen hinzugefügt und gehofft, das es reicht (ich brauche für lvalue-references und rvalue-references dezent anderes Verhalten).

    Hast Du nur um das zu umgehen die Überladungen hinzugefügt, oder bräuchtest Du sie ohnehin? Und wie stark variieren die Rueckgabetypen relativ zur value category der Argumente? Wenn nicht, dann kannst Du ja einen generischen operator==(T&&,U&&) definieren, der intern via if constexpr differenziert.

    Die Rückgabewerte variieren zwischen den beiden Kategorien gar nicht, da in beiden Fällen ein Drittobjekt zurückgeliefert wird. Im rvalue-ref Fall sollen die Member dieses Objekts jedoch vorzugsweise via Move-Ctor erstellt werden.


  • Mod

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Es geht im Endeffekt darum, dass ich gerne für rvalue referenzen das Source-Object entmoven möchte, statt zu kopieren und daher auch rvalue-refs statt einer lvalue-ref weiter geben.

    Du willst von einem Vergleichsoperanden moven? Klingt irgendwie falsch?



  • @Columbo sagte in [c++20] rewritten operator == und clang:

    @DNKpp sagte in [c++20] rewritten operator == und clang:

    Es geht im Endeffekt darum, dass ich gerne für rvalue referenzen das Source-Object entmoven möchte, statt zu kopieren und daher auch rvalue-refs statt einer lvalue-ref weiter geben.

    Du willst von einem Vergleichsoperanden moven? Klingt irgendwie falsch?

    Wie gesagt. Es ist kein Vergleich per se, sondern eine Komposition von zwei (oder mehr) Prädikaten zu einer Prädikatskomposition (also ein neues Prädikat, mit welchem man das gleiche Spiel wiederholen kann). Prädikate sind in meinem Fall einfach wrapper um Funktionsobjekte herum und genau diese Funktionsobjekte sollen entsprechend im Rückgabeobjekt als value landen; entweder copy- oder move-constructed.

    Edit:
    Ich glaube der Weg mit einem freien operator == löst glaube ich mein Problem. Wenn ich mich nicht täusche, dann macht das hier genau das, was ich haben möchte:

    template <class TLhs, class TRhs>
        requires derived_from_unified_base<TLhs, equal_tag>
            || derived_from_unified_base<TRhs, equal_tag>
    constexpr auto operator ==(TLhs&& lhs, TRhs&& rhs)
    {
        return action(std::forward<TLhs>(lhs), std::forward<TRhs>(rhs));
    }
    

  • Mod

    @DNKpp Ja, so hatte ich mir das vorgestellt.

    Wie gesagt. Es ist kein Vergleich per se, sondern eine Komposition von zwei (oder mehr) Prädikaten zu einer Prädikatskomposition (also ein neues Prädikat, mit welchem man das gleiche Spiel wiederholen kann). Prädikate sind in meinem Fall einfach wrapper um Funktionsobjekte herum und genau diese Funktionsobjekte sollen entsprechend im Rückgabeobjekt als value landen; entweder copy- oder move-constructed.

    Ich haette erwartet, dass diese Objekte keine dynamischen Speicher allozieren und deshalb eine Kopie nicht teurer waere, aber ich verstehe, was Du meinst.



  • @Columbo sagte in [c++20] rewritten operator == und clang:

    @DNKpp Ja, so hatte ich mir das vorgestellt.

    Wie gesagt. Es ist kein Vergleich per se, sondern eine Komposition von zwei (oder mehr) Prädikaten zu einer Prädikatskomposition (also ein neues Prädikat, mit welchem man das gleiche Spiel wiederholen kann). Prädikate sind in meinem Fall einfach wrapper um Funktionsobjekte herum und genau diese Funktionsobjekte sollen entsprechend im Rückgabeobjekt als value landen; entweder copy- oder move-constructed.

    Ich haette erwartet, dass diese Objekte keine dynamischen Speicher allozieren und deshalb eine Kopie nicht teurer waere, aber ich verstehe, was Du meinst.

    Berechtigte Annahme, aber ich weiß natürlich nicht, was irgendwelche Third-Party-Funktionsobjekte capturen. Macht imo schon durchaus Sinn das auch dementsprechend sauber zu supporten. Im Prinzip ist jeder Funktionspointer oder -objekt, welches ein bool returned, ein valider Kandidat um gewrapped zu werden.

    Danke für deine Hilfe 😉


Anmelden zum Antworten