Wdrożenie operatorów porównania za pomocą „krotki” i „remisu”, dobry pomysł?

98

(Uwaga: tuplei tiemożna je pobrać z Boost lub C ++ 11.)
Podczas pisania małych struktur z tylko dwoma elementami, czasami wybieram std::pair, ponieważ wszystkie ważne rzeczy są już zrobione dla tego typu danych, jak operator<na przykład porządkowanie ścisłe-słabe .
Wadą są jednak prawie bezużyteczne nazwy zmiennych. Nawet jeśli sam to stworzyłem typedef, dwa dni później nie pamiętam, co firsti co seconddokładnie było, zwłaszcza jeśli są tego samego typu. Sytuacja jest jeszcze gorsza w przypadku więcej niż dwóch członków, ponieważ zagnieżdżanie się pairjest do niczego.
Inną opcją jesttuple, albo z Boost, albo C ++ 11, ale to naprawdę nie wygląda ładniej i wyraźniej. Więc wracam do pisania struktur samodzielnie, włączając wszelkie potrzebne operatory porównania.
Ponieważ szczególnie operator<może to być dość uciążliwe, pomyślałem o obejściu tego całego bałaganu, polegając tylko na operacjach zdefiniowanych dla tuple:

Przykład operator<, np. Dla ścisłego-słabego uporządkowania:

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

( tieSprawia, że tuplez T&referencjami od przekazanych argumentów.)


Edycja : sugestia @DeadMG dotycząca prywatnego dziedziczenia z tuplenie jest zła, ale ma kilka wad:

  • Jeśli operatorzy są wolnostojący (prawdopodobnie przyjaciele), muszę dziedziczyć publicznie
  • Dzięki rzutowaniu moje funkcje / operatory (w operator=szczególności) można łatwo ominąć
  • Dzięki tie rozwiązaniu mogę pominąć niektórych członków, jeśli nie mają znaczenia przy składaniu zamówienia

Czy są jakieś wady tej implementacji, które muszę wziąć pod uwagę?

Xeo
źródło
1
Wydaje
1
To bardzo sprytny pomysł, nawet jeśli się nie uda. Muszę to zbadać.
templatetypedef
Wygląda to całkiem rozsądnie. Jedyną pułapką, o której mogę teraz pomyśleć, jest to, że tienie można jej zastosować do elementów z polami bitowymi.
Ise Wisteria
4
Podoba mi się ten pomysł! Jeśli tie(...)wywołania mają być zduplikowane w różnych operatorach (=, ==, <itd.), Możesz napisać prywatną metodę wbudowaną, make_tuple(...)aby ją hermetyzować, a następnie wywołać ją z różnych innych miejsc, jak w return lhs.make_tuple() < rhs.make_tuple();(chociaż typ zwracany z ta metoda może być fajna do zadeklarowania!)
aldo
13
@aldo: C ++ 14 na ratunek! auto tied() const{ return std::tie(the, members, here); }
Xeo

Odpowiedzi:

61

To z pewnością ułatwi napisanie poprawnego operatora niż samodzielne przewijanie. Powiedziałbym, że rozważ inne podejście tylko wtedy, gdy profilowanie pokazuje, że operacja porównania jest czasochłonną częścią aplikacji. W przeciwnym razie łatwość utrzymania tego powinna przeważyć nad ewentualnymi obawami dotyczącymi wydajności.

Mark B.
źródło
17
Nie mogę sobie wyobrazić sytuację, w której tuple<>nic nie operator<będzie żadnych wolniej niż odręczny jeden.
ildjarn
51
Raz wpadłem na dokładnie ten sam pomysł i trochę poeksperymentowałem. Byłem pozytywnie zaskoczony, widząc, że kompilator wprowadził i zoptymalizował wszystko , co ma związek z krotkami i referencjami, emitując asembler prawie identyczny z kodem napisanym ręcznie.
JohannesD
7
@JohannesD: Mogę poprzeć to świadectwo, zrobiłem to samo raz
patrz
Czy to gwarantuje ścisłe, słabe zamówienia ? W jaki sposób?
CinCout
5

Natknąłem się na ten sam problem, a moje rozwiązanie wykorzystuje szablony wariadyczne C ++ 11. Oto kod:

Część .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...);
  }
}

Oraz .cpp dla przypadku podstawowego bez argumentów:

bool lexiLessthan()
{
  return false;
}

Teraz twój przykład staje się:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
user2369060
źródło
Wprowadziłem tutaj podobne rozwiązanie, ale nie wymagające operatora! =. stackoverflow.com/questions/11312448/…
steviekm3
3

Moim zdaniem nadal nie rozwiązujesz tego samego problemu, co std::tuplerozwiązania - mianowicie musisz wiedzieć zarówno ile, jak i nazwę każdej zmiennej składowej, powielasz ją dwukrotnie w funkcji. Możesz zdecydować się na privatedziedziczenie.

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

Na początku takie podejście jest trochę bardziej kłopotliwe, ale utrzymujesz zmienne i nazwy tylko w jednym miejscu, a nie w każdym miejscu dla każdego operatora, którego chcesz przeciążać.

Szczeniak
źródło
3
Więc używałbym nazwanych metod dostępu do zmiennych, takich jak T& one_member(){ return std::get<0>(*this); }itp.? Ale czy nie wymagałoby to ode mnie zapewnienia takiej metody dla każdego „elementu członkowskiego”, który mam, w tym przeciążeń dla wersji stałej i innej niż stała?
Xeo
@Xeo Nie uważam, aby nazwane metody dostępu wymagały więcej pracy niż tworzenie rzeczywistych zmiennych. Tak czy inaczej, musisz mieć osobną nazwę dla każdej zmiennej. Przypuszczam, że byłoby duplikowanie dla const / non-const. Możesz jednak szablonować całą tę pracę.
Lee Louviere
1

Jeśli planujesz użyć więcej niż jednego przeciążenia operatora lub więcej metod z krotki, polecam utworzenie krotki jako elementu członkowskiego klasy lub pochodnej z krotki. W przeciwnym razie to, co robisz, to dużo więcej pracy. Decydując się między nimi, ważne pytanie, na które należy odpowiedzieć, brzmi: Czy chcesz, aby Twoja klasa była krotką? Jeśli nie, polecam zawarcie krotki i ograniczenie interfejsu za pomocą delegowania.

Możesz utworzyć metody dostępu, aby „zmienić nazwę” członków krotki.

Lee Louviere
źródło
Czytałem pytanie PO jako oznaczające „realizuje moją klasę operator<stosując std::tierozsądne?” Nie rozumiem, jak ta odpowiedź odnosi się do tego pytania.
ildjarn
@ildjarn Jest kilka komentarzy, których tutaj nie zamieściłem. Skompilowałem wszystko, żeby lepiej czytać.
Lee Louviere