Eine gute Idee ist es, Vergleichsoperatoren über 'Tupel' und 'Krawatte' zu implementieren?

98

(Hinweis: tupleund tiekann aus Boost oder C ++ 11 übernommen werden.)
Wenn ich kleine Strukturen mit nur zwei Elementen schreibe, neige ich manchmal dazu, a zu wählen std::pair, da alle wichtigen Dinge für diesen Datentyp bereits erledigt sind, wie operator<zum Beispiel für strikt schwache Ordnungen .
Die Nachteile sind jedoch die ziemlich nutzlosen Variablennamen. Selbst wenn ich das selbst geschaffen habe typedef, werde ich mich 2 Tage später nicht mehr daran erinnern, was firstund was secondgenau war, besonders wenn beide vom gleichen Typ sind. Dies wird für mehr als zwei Mitglieder noch schlimmer, da das Verschachteln pairziemlich scheiße ist.
Die andere Option dafür ist atuple, entweder von Boost oder C ++ 11, aber das sieht nicht wirklich schöner und klarer aus. Also schreibe ich die Strukturen wieder selbst, einschließlich aller erforderlichen Vergleichsoperatoren.
Da besonders das operator<ziemlich umständlich sein kann, dachte ich daran, dieses ganze Durcheinander zu umgehen, indem ich mich nur auf die Operationen stütze, die definiert sind für tuple:

Beispiel operator<zB für streng-schwache Ordnung:

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

( tieMacht einen tuplevon T&Referenzen aus den übergebenen Argumenten.)


Bearbeiten : Der Vorschlag von @DeadMG, privat zu erben, tupleist nicht schlecht, hat aber einige Nachteile:

  • Wenn die Betreiber freistehend sind (möglicherweise Freunde), muss ich öffentlich erben
  • Beim Casting können meine Funktionen / Operatoren ( operator=speziell) leicht umgangen werden
  • Mit der tieLösung kann ich bestimmte Mitglieder auslassen, wenn sie für die Bestellung keine Rolle spielen

Gibt es irgendwelche Nachteile bei dieser Implementierung, die ich berücksichtigen muss?

Xeo
quelle
1
Sieht für mich völlig vernünftig aus ...
ildjarn
1
Das ist eine sehr clevere Idee, auch wenn sie nicht aufgeht. Ich werde das untersuchen müssen.
Templatetypedef
Das sieht ziemlich vernünftig aus. Die einzige Gefahr, an die ich jetzt denken kann, ist, dass tiesie nicht auf Bitfeldmitglieder angewendet werden kann.
Ise Wisteria
4
Ich mag diese Idee! Wenn die tie(...)Aufrufe in verschiedenen Operatoren (=, ==, <usw.) dupliziert werden sollen, können Sie eine private Inline-Methode schreiben, um dies make_tuple(...)zu kapseln, und sie dann von den verschiedenen anderen Stellen aus aufrufen, wie in return lhs.make_tuple() < rhs.make_tuple();(obwohl der Rückgabetyp von Diese Methode könnte Spaß machen zu erklären!)
Aldo
13
@aldo: C ++ 14 zur Rettung! auto tied() const{ return std::tie(the, members, here); }
Xeo

Antworten:

60

Dies wird es sicherlich einfacher machen, einen korrekten Operator zu schreiben, als ihn selbst zu rollen. Ich würde sagen, dass Sie einen anderen Ansatz nur in Betracht ziehen, wenn die Profilerstellung zeigt, dass der Vergleichsvorgang ein zeitaufwändiger Teil Ihrer Anwendung ist. Andernfalls sollte die einfache Wartung die möglichen Leistungsprobleme überwiegen.

Mark B.
quelle
17
Ich kann mir keinen Fall vorstellen, in tuple<>dem operator<es langsamer wäre als ein handgeschriebener.
ildjarn
51
Ich hatte genau die gleiche Idee einmal und experimentierte. War positiv überrascht zu sehen, dass der Compiler alles , was mit Tupeln und Referenzen zu tun hat, inline und optimiert hat und eine Assembly ausgibt , die fast identisch mit handgeschriebenem Code ist.
JohannesD
7
@ JohannesD: Ich kann dieses Zeugnis unterstützen, habe das gleiche einmal
getan
Garantiert dies eine strikte schwache Bestellung ? Wie?
CinCout
5

Ich bin auf dasselbe Problem gestoßen und meine Lösung verwendet verschiedene C ++ 11-Vorlagen. Hier kommt der Code:

Der .h Teil:

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

Und die .cpp für den Basisfall ohne Argumente:

bool lexiLessthan()
{
  return false;
}

Jetzt wird Ihr Beispiel:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
user2369060
quelle
Ich habe hier eine ähnliche Lösung angegeben, benötige aber nicht den Operator! =. stackoverflow.com/questions/11312448/…
steviekm3
3

Meiner Meinung nach std::tuplesprechen Sie immer noch nicht das gleiche Problem wie die Lösungen an. Sie müssen nämlich sowohl die Anzahl als auch den Namen jeder Mitgliedsvariablen kennen und diese zweimal in der Funktion duplizieren. Sie könnten sich für die privateVererbung entscheiden.

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

Dieser Ansatz ist anfangs etwas chaotischer, aber Sie verwalten die Variablen und Namen nur an einem Ort und nicht an jedem Ort für jeden Operator, den Sie überladen möchten.

Hündchen
quelle
3
Also würde ich benannte Accessoren für Variablen wie T& one_member(){ return std::get<0>(*this); }etc verwenden? Aber müsste ich dann nicht für jedes "Mitglied", das ich habe, eine solche Methode bereitstellen, einschließlich Überladungen für die const- und non-const-Version?
Xeo
@Xeo Ich sehe nicht, dass benannte Accessoren mehr Arbeit erfordern als das Erstellen tatsächlicher Variablen. In beiden Fällen müssten Sie für jede Variable einen eigenen Namen haben. Ich nehme an, es würde eine Duplizierung für const / non-const geben. Sie können jedoch all diese Arbeiten vorlegen.
Lee Louviere
1

Wenn Sie planen, mehr als eine Operatorüberladung oder mehrere Methoden aus Tupel zu verwenden, würde ich empfehlen, Tupel zu einem Mitglied der Klasse zu machen oder von Tupel abzuleiten. Ansonsten ist das, was Sie tun, viel mehr Arbeit. Wenn zwischen den beiden zu entscheiden, eine wichtige Frage zu beantworten ist: Haben Sie Ihre Klasse wollen sein ein Tupel? Wenn nicht, würde ich empfehlen, ein Tupel zu enthalten und die Schnittstelle durch Delegierung einzuschränken.

Sie können Accessoren erstellen, um die Mitglieder des Tupels "umzubenennen".

Lee Louviere
quelle
Ich habe die Frage des OP mit der Bedeutung " operator<Ist es std::tievernünftig, meine Klasse zu implementieren ?" Gelesen. Ich verstehe nicht, wie sich diese Antwort auf diese Frage bezieht.
ildjarn
@ildjarn Es gibt einige Kommentare, die ich hier nicht gepostet habe. Ich habe alles zusammengestellt, damit es besser liest.
Lee Louviere