Probleme mit std::sort, std::ranges::sort und eigenem < Operator
-
Zu schnell, man sollte sich mehr Mühe geben.
-
JA:
Ja, das kommt sehr häufig vor. Wenn du std::ranges::sort für eine Liste von eigenen Objekten aufrufst, muss die Funktion die Objekte miteinander vergleichen können. Dafür benötigt sie Zugriff auf den Kleiner-als-Operator (<) oder eine benutzerdefinierte Vergleichsfunktion. [1]
Wenn die Datenfelder deiner Klasse privat (private) sind, musst du diesen Operator oder die Funktion oft als friend deklarieren, damit er auf die internen Werte zugreifen darf.Warum friend bei std::ranges::sort wichtig ist
Standardmäßig nutzt std::ranges::sort den Operator <. In modernem C++ (ab C++20) nutzt man hierfür idealerweise den Drei-Wege-Vergleichsoperator (<=>), auch bekannt als Spaceship-Operator.
Da dieser Operator außerhalb der Klasse als freie Funktion agiert oder die Argumente symmetrisch behandeln muss, benötigt er das Schlüsselwort friend, um die privaten Attribute der Klasse zu lesen.Vereinfacht gesagt: Dein compare op muss unabhängig vom Kontext der Klassen verwendet werden können, aber gleichzeitig Zugriff auf die Member der Klasse erhalten.
-
@john-0 sagte in Probleme mit std::sort, std::ranges::sort und eigenem < Operator:
Du musst den neuen operator<=> definieren! Dann übersetzt das auch problemlos.
Ja, das sollte man auch beachten.
-
Ok, der Compiler mag mich überhaupt nicht. Wenn ich Folgendes
#include <algorithm> #include <vector> #include <iostream> class Foo { int value; public: explicit Foo (const int v) : value(v) {} auto operator<=> (const Foo& rhs) const = default; int get() const { return this->value; } }; int main() { std::vector<Foo> vec = { Foo{5}, Foo{1}, Foo{9}, Foo{3} }; std::ranges::sort(vec); for (const auto& f : vec) { std::cout << f.get() << ' '; } std::cout << "\n"; }eingebe übersetzt er es problemlos, und es gibt ein richtiges Ergebnis. Wenn ich versuch den operator<=> selbst zu schreiben, wirft er mir einen Fehler. Momentan hadere ich damit, dass ich das nicht nachvollziehen kann.
-
dass der Compiler bei deinem selbstgeschriebenen operator<=> streikt, liegt an den strengen Concepts von C++20 Ranges.
Damit ein Typ mit std::ranges::sort sortiert werden kann, muss er das Concept std::sortable erfüllen. Dieses baut wiederum auf std::totally_ordered auf. Das bedeutet für dich: Ein Typ ist für Ranges nur dann sortierbar, wenn er sowohl eine Ordnung (<) als auch eine Gleichheit (==) besitzt.Warum funktioniert = default?
Wenn du auto operator<=>(const Foo&) const = default; schreibst, ist der Compiler so nett und generiert für dich implizit auch gleich den passenden Gleichheitsoperator (operator==) mit. Damit ist die Bedingung für std::totally_ordered erfüllt.
Warum scheitert die manuelle Version?
Wenn du den operator<=> von Hand schreibst, generiert der Compiler den operator== nicht automatisch. std::ranges::sort sieht also eine Klasse, die man zwar vergleichen, aber nicht auf Gleichheit prüfen kann – und bricht mit einem kryptischen Compiler-Fehler ab.
Die Lösung
Wenn du den Spaceship-Operator selbst implementierst, musst du den operator== entweder ebenfalls manuell schreiben oder ihn separat via = default anfordern:
class Foo { int value;public: explicit Foo(const int v) : value(v) {} // 1. Dein manueller Spaceship-Operator für die Ordnung auto operator<=>(const Foo& rhs) const { return this->value <=> rhs.value; } // 2. WICHTIG FÜR RANGES: Der Gleichheitsoperator (kann defaulted werden) bool operator==(const Foo& rhs) const = default; int get() const { return this->value; } };Sobald der operator== vorhanden ist, kompiliert dein Code auch mit der manuellen Variante fehlerfrei.
-
@Anon-81275 sagte in Probleme mit std::sort, std::ranges::sort und eigenem < Operator:
Warum scheitert die manuelle Version?
Wenn du den operator<=> von Hand schreibst, generiert der Compiler den operator== nicht automatisch. std::ranges::sort sieht also eine Klasse, die man zwar vergleichen, aber nicht auf Gleichheit prüfen kann – und bricht mit einem kryptischen Compiler-Fehler ab.
Aua, der operator<=> wurde doch eingeführt, weil er eben direkt „Kleiner als“, „Gleichheit“ und „Größer als“ ausdrücken kann, und da muss man explizit die Gleichheit nochmals implementieren? Kein Wunder, wenn C++ immer ins Hintertreffen gerät – das ist unlogisch. Früher wusste man, dass operator== und operator< zu implementieren ist.
Danke für den Hinweis.
-
Ja, ich bin auch kein C++-"Stammspieler", aber ich sehe das so ähnlich wie du. Ich glaube, das ist so etwas ähnliches wie Ballast, der einfach die Komplexität erhöht. Mir fällt jedenfalls spontan kein sinnvoller Use-Case ein.
-
Danke Leute, ich habe die Operatoren <=> und == definiert und damit funktioniert das std::ranges::sort auch.
-
@john-0 sagte in Probleme mit std::sort, std::ranges::sort und eigenem < Operator:
Kein Wunder, wenn C++ immer ins Hintertreffen gerät – das ist unlogisch.
Uff, ehrlich gesagt juckt mich das nicht.
Das muss man wissen, man könnte es auch als eine Macke bezeichnen, aber die Schönheit eine Sprache ist eine Sache, die Stilblüten der Benutzer eine andere.
Ich sehe da gerade eine Design-Pattern Seuche.
class UART_Base { } class UART_1 : public UART_Base { UART_1* GetInstance()... } UART_BASE* GetInstance(int Mode) { if (Mode == 1) return UART_1.GetInstance(); if (mode == 2) return UART_2.GetInstance(); }Und gerade sehe ich in der Android Welt so eine schöne Mischung aus Java und Kotlin. Gradle ist so lahm wie eh und je. Man schießt gerne mit Kanonen auf Spatzen in dem man eine z.B. eine Kommunikation IO in einen eigenen Prozess auslagert und mittels Sockets darauf zugreift.
-
@Quiche-Lorraine sagte in Probleme mit std::sort, std::ranges::sort und eigenem < Operator:
die Stilblüten der Benutzer eine andere.
... für mich ist das eine pauschale Abwertung. Es hat doch nicht mehr mit Geschmack zu tun, wenn eine Sprache derart verschlimmbessert wird, dafür gibt es sogar objektive Kriterien bzw. Metriken.
Wenn es für ein Feature keinen Mehrwert gibt, aber ich nun für das gleiche Ergebnis mehr als vorher schreiben muss, oder Bestandscode unnötig legacy wird, dann verfehlt das Feature den Zweck. Also, höherer Aufwand, gleicher Nutzen.
Ja, nicht jeder mag Rindfleisch, aber deshalb leben Vegetarier auch nicht gesünder.
Sorry, musste das einmal rauslassen.