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


98

(Nota: tuplee tiepuò essere preso da Boost o C ++ 11.)
Quando si scrivono strutture piccole con solo due elementi, a volte tendo a scegliere a std::pair, poiché tutte le cose importanti sono già fatte per quel tipo di dati, come operator<per l'ordinamento rigoroso-debole .
Gli svantaggi però sono i nomi delle variabili praticamente inutili. Anche se l'ho creato io stesso typedef, non ricorderò 2 giorni dopo cosa firste cosa secondfosse esattamente, soprattutto se sono entrambi dello stesso tipo. Questo diventa anche peggio per più di due membri, dato che l'annidamento fa pairschifo.
L'altra opzione è atuple, sia da Boost che da C ++ 11, ma in realtà non sembra più bello e chiaro. Quindi torno a scrivere le strutture da solo, inclusi gli operatori di confronto necessari.
Dato che soprattutto operator<può essere piuttosto macchinoso, ho pensato di aggirare tutto questo casino affidandomi semplicemente alle operazioni definite per 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);
}

( tiecrea una serie tupledi T&riferimenti dagli argomenti passati.)


Modifica : il suggerimento di @DeadMG di ereditare privatamente da tuplenon è cattivo, ma ha alcuni svantaggi:

  • Se gli operatori sono indipendenti (possibilmente amici), devo ereditare pubblicamente
  • Con il casting, le mie funzioni / operatori (in operator=particolare) possono essere facilmente aggirati
  • Con la tiesoluzione, posso escludere alcuni membri se non sono importanti per l'ordinazione

Ci sono degli svantaggi in questa implementazione che devo considerare?


1
Mi sembra perfettamente ragionevole ...
ildjarn

1
È un'idea molto intelligente, anche se non riesce. Dovrò indagare su questo.
templatetypedef

Sembra abbastanza ragionevole. L'unico problema a cui riesco a pensare ora è che tienon può essere applicato ai membri del campo di bit.
Ise Wisteria

4
Mi piace questa idea! Se le tie(...)chiamate verranno duplicate in vari operatori (=, ==, <, ecc.), Potresti scrivere un metodo inline privato make_tuple(...)per incapsularlo e poi chiamarlo da vari altri posti, come in return lhs.make_tuple() < rhs.make_tuple();(sebbene il tipo di ritorno da che metodo potrebbe essere divertente da dichiarare!)
aldo

13
@aldo: C ++ 14 in soccorso! auto tied() const{ return std::tie(the, members, here); }
Xeo

Risposte:


60

Questo renderà sicuramente più facile scrivere un operatore corretto che eseguirlo da soli. Direi che prendere in considerazione un approccio diverso solo se la profilazione mostra che l'operazione di confronto è una parte che richiede tempo della tua applicazione. In caso contrario, la facilità di manutenzione dovrebbe superare qualsiasi possibile problema di prestazioni.


17
Non riesco a immaginare un caso in cui tuple<>'s operator<sarebbero qualsiasi più lento di uno scritto a mano.
ildjarn

51
Una volta ho avuto la stessa identica idea e ho fatto degli esperimenti. Sono rimasto positivamente sorpreso di vedere che il compilatore ha integrato e ottimizzato tutto ciò che ha a che fare con tuple e riferimenti, emettendo assembly quasi identici al codice scritto a mano.
JohannesD

7
@JohannesD: posso sostenere quella testimonianza, ho fatto lo stesso una volta
vedi il

Questo garantisce un ordine rigoroso e debole ? Come?
CinCout

5

Mi sono imbattuto in 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<typename T, typename... Args>
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
);

Ho inserito una soluzione simile qui ma non richiede l'operatore! =. stackoverflow.com/questions/11312448/...
steviekm3

3

A mio parere, non stai ancora affrontando lo stesso problema delle std::tuplesoluzioni, vale a dire, devi sapere sia quanti che il nome di ogni variabile membro, lo stai duplicando due volte nella funzione. Potresti optare per l' privateeredità.

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

Questo approccio è un po 'più complicato per cominciare, ma stai solo mantenendo le variabili ei nomi in un posto, invece che in ogni posto per ogni operatore che desideri sovraccaricare.


3
Quindi utilizzerei funzioni di accesso con nome per le variabili come T& one_member(){ return std::get<0>(*this); }ecc.? Ma non sarebbe necessario che fornissi un tale metodo per ogni "membro" che ho, inclusi gli overload per la versione const e non const?
Xeo

@Xeo Non credo che le funzioni di accesso con nome richiedano altro lavoro rispetto alla creazione di variabili effettive. In entrambi i casi dovresti avere un nome separato per ogni variabile. Suppongo che ci sarebbe una duplicazione per const / non-const. Tuttavia, puoi modellare tutto questo lavoro.
Lee Louviere

1

Se si prevede di utilizzare più di un overload dell'operatore o più metodi da tuple, si consiglia di rendere tuple un membro della classe o di derivare da tuple. Altrimenti, quello che stai facendo è molto più lavoro. Quando si decide tra i due, una domanda importante a cui rispondere è: vuoi che lo faccia la tua classe sia una tupla? In caso contrario, consiglierei di contenere una tupla e di limitare l'interfaccia utilizzando la delega.

È possibile creare funzioni di accesso per "rinominare" i membri della tupla.


Ho letto la domanda dell'OP nel senso che significa "implementare la mia classe ' operator<usando std::tieragionevole?" Non capisco come questa risposta sia collegata a quella domanda.
ildjarn

@ildjarn Ci sono alcuni commenti che non ho pubblicato qui. Ho compilato tutto in modo che legga meglio.
Lee Louviere
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.