Implementare operatori di confronto tramite ‘tuple’ e ‘tie’, una buona idea?

(Nota: tuple e tie possono essere presi da Boost o C ++ 11.)
Quando si scrivono piccole strutture con solo due elementi, a volte tendo a scegliere una std::pair come tutte le cose importanti sono già state fatte per quel tipo di dati, come l’ operator< per l’ordinamento rigoroso-debole.
Gli svantaggi però sono i nomi delle variabili praticamente inutili. Anche se io stesso ho creato quel typedef , non ricorderò 2 giorni dopo quale sia esattamente il second e il second , specialmente se sono entrambi dello stesso tipo. Questo peggiora anche per più di due membri, dato che le pair nidificanti fanno quasi schifo.
L’altra opzione è una tuple , sia da Boost che da C ++ 11, ma non sembra davvero più bella e chiara. Quindi torno a scrivere le strutture personalmente, compresi eventuali operatori di confronto necessari.
Dal momento che in particolare l’ operator< può essere piuttosto ingombrante, ho pensato di aggirare questo pasticcio semplicemente facendo affidamento sulle operazioni definite per la tuple :

Esempio di operator< , ad esempio per l’ordinamento rigoroso debole:

 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); } 

( tie fa una tuple di T& riferimenti dagli argomenti passati.)


Modifica : il suggerimento da @DeadMG di ereditare privatamente da tuple non è negativo, ma ha alcuni svantaggi:

  • Se gli operatori sono indipendenti (possibilmente amici), ho bisogno di ereditare pubblicamente
  • Con il casting, le mie funzioni / operatori ( operator= specificatamente) possono essere facilmente aggirati
  • Con la soluzione di tie , posso escludere alcuni membri se non sono importanti per l’ordine

Ci sono degli svantaggi in questa implementazione che devo considerare?

Questo sicuramente renderà più facile scrivere un operatore corretto piuttosto che farlo da solo. Direi che consideriamo solo un approccio diverso se la profilatura mostra l’operazione di confronto come una parte della tua applicazione che richiede molto tempo. In caso contrario, la facilità di mantenere questo dovrebbe superare qualsiasi ansible problema di prestazioni.

Sono arrivato attraverso questo stesso problema e la mia soluzione utilizza modelli variadici c ++ 11. Ecco il codice:

La parte .h:

 /*** * 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 bool lexiLessthan(const T &first, const T &second, Args... rest) { if (first != second) { return first < second; } else { return lexiLessthan(rest...); } } 

E il .cpp per il caso base senza argomenti:

 bool lexiLessthan() { return false; } 

Ora il tuo esempio diventa:

 return lexiLessthan( lhs.one_member, rhs.one_member, lhs.another, rhs.another, lhs.yet_more, rhs.yet_more ); 

A mio parere, non stai ancora affrontando lo stesso problema di std::tuple risolve – cioè, devi conoscere sia il numero e il nome di ogni variabile membro, lo stai duplicando due volte nella funzione. Potresti optare per private ereditarietà private .

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

Questo approccio è un po ‘più complicato per cominciare, ma stai mantenendo le variabili e i nomi in un unico posto, invece che in ogni posto per ogni operatore che desideri sovraccaricare.

Se si prevede di utilizzare più di un overload di operatore o più metodi da tuple, si consiglia di rendere tuple un membro della class o derivare da tuple. Altrimenti, quello che stai facendo è molto più lavoro. Al momento di decidere tra i due, una domanda importante a cui rispondere è: vuoi che la tua class sia una tupla? In caso contrario, consiglierei di contenere una tupla e di limitare l’interfaccia usando la delega.

È ansible creare accessors per “rinominare” i membri della tupla.