Menerapkan operator perbandingan melalui 'tuple' dan 'tie', ide bagus?

98

(Catatan: tupledan tiedapat diambil dari Boost atau C ++ 11.)
Saat menulis struct kecil dengan hanya dua elemen, saya terkadang cenderung memilih a std::pair, karena semua hal penting sudah dilakukan untuk tipe data itu, seperti operator<untuk pengurutan yang ketat-lemah .
Kerugiannya adalah nama variabel yang tidak berguna. Bahkan jika saya sendiri yang membuatnya typedef, saya tidak akan ingat 2 hari kemudian apa firstdan apa secondsebenarnya itu, terutama jika keduanya memiliki tipe yang sama. Ini menjadi lebih buruk untuk lebih dari dua anggota, karena bersarang paircukup menyebalkan.
Opsi lain untuk itu adalah atuple, baik dari Boost atau C ++ 11, tetapi itu tidak terlihat lebih bagus dan lebih jelas. Jadi saya kembali menulis struct sendiri, termasuk operator perbandingan yang diperlukan.
Karena khususnya operator<bisa sangat rumit, saya berpikir untuk menghindari seluruh kekacauan ini hanya dengan mengandalkan operasi yang ditentukan untuk tuple:

Contoh dari operator<, misalnya untuk pengurutan-ketat-lemah:

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

( tieMembuat tupledari T&referensi dari argumen berlalu.)


Sunting : Saran dari @DeadMG untuk mewarisi secara pribadi tuplebukanlah hal yang buruk, tetapi ada beberapa kekurangan:

  • Jika operatornya berdiri bebas (mungkin teman), saya perlu mewarisi secara publik
  • Dengan transmisi, fungsi / operator saya ( operator=secara khusus) dapat dengan mudah dilewati
  • Dengan tiesolusinya, saya dapat meninggalkan anggota tertentu jika mereka tidak peduli dengan pemesanan

Apakah ada kekurangan dalam penerapan ini yang perlu saya pertimbangkan?

Xeo
sumber
1
Terlihat sangat masuk akal bagi saya ...
ildjarn
1
Itu ide yang sangat cerdas, meskipun tidak berhasil. Saya harus menyelidiki ini.
templatetypedef
Ini terlihat cukup masuk akal. Satu-satunya perangkap yang dapat saya pikirkan sekarang adalah yang tietidak dapat diterapkan pada anggota bit-field.
Ise Wisteria
4
Aku suka ide ini! Jika tie(...)panggilan akan diduplikasi di berbagai operator (=, ==, <, dll.) Anda dapat menulis metode inline pribadi make_tuple(...)untuk merangkumnya dan kemudian memanggilnya dari berbagai tempat lain, seperti di return lhs.make_tuple() < rhs.make_tuple();(meskipun tipe kembalian dari metode itu bisa menyenangkan untuk diumumkan!)
aldo
13
@aldo: C ++ 14 untuk menyelamatkan! auto tied() const{ return std::tie(the, members, here); }
Xeo

Jawaban:

61

Hal ini tentunya akan mempermudah penulisan operator yang benar daripada menggulungnya sendiri. Saya katakan hanya pertimbangkan pendekatan yang berbeda jika pembuatan profil menunjukkan operasi perbandingan menjadi bagian yang memakan waktu dari aplikasi Anda. Jika tidak, kemudahan pemeliharaan ini harus lebih besar daripada kemungkinan masalah kinerja.

Mark B
sumber
17
Aku tidak bisa membayangkan kasus di mana tuple<>'s operator<akan ada lebih lambat dari satu tulisan tangan.
ildjarn
51
Saya mendapat ide yang persis sama sekali, dan melakukan beberapa eksperimen. Sangat terkejut melihat bahwa kompilator menyisipkan dan mengoptimalkan segala sesuatu yang berkaitan dengan tupel dan referensi, memancarkan rakitan yang hampir identik dengan kode tulisan tangan.
JohannesD
7
@JohannesD: Saya dapat mendukung kesaksian itu, melakukan hal yang sama sekali
lihat
Apakah ini menjamin pemesanan sangat lemah ? Bagaimana?
CinCout
5

Saya telah menemukan masalah yang sama ini dan solusi saya menggunakan template variadic c ++ 11. Inilah kodenya:

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

Dan .cpp untuk kasus dasar tanpa argumen:

bool lexiLessthan()
{
  return false;
}

Sekarang contoh Anda menjadi:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
pengguna2369060
sumber
Saya memasukkan solusi serupa di sini tetapi tidak membutuhkan operator! =. stackoverflow.com/questions/11312448/…
steviekm3
3

Menurut pendapat saya, Anda masih belum menangani masalah yang sama dengan std::tuplepemecahannya - yaitu, Anda harus tahu berapa banyak dan nama setiap variabel anggota, Anda menduplikasinya dua kali dalam fungsi. Anda bisa memilih privatewarisan.

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

Pendekatan ini adalah sedikit sedikit lebih berantakan untuk memulai dengan, tetapi Anda hanya mempertahankan variabel dan nama di satu tempat, bukan di setiap tempat untuk setiap operator yang ingin Anda overload.

Anak anjing
sumber
3
Jadi saya akan menggunakan pengakses bernama ke variabel seperti T& one_member(){ return std::get<0>(*this); }dll? Tapi bukankah itu membutuhkan saya untuk menyediakan metode seperti itu untuk setiap "anggota" yang saya miliki, termasuk kelebihan beban untuk versi const dan non-const?
Xeo
@Xeo Saya tidak melihat pengakses bernama membutuhkan lebih banyak pekerjaan daripada membuat variabel aktual. Anda harus memiliki nama terpisah untuk setiap variabel. Saya kira akan ada duplikasi untuk const / non-const. Namun, Anda dapat membuat template semua pekerjaan ini.
Lee Louviere
1

Jika Anda berencana untuk menggunakan lebih dari satu operator overload, atau lebih banyak metode dari tuple, saya sarankan menjadikan tuple sebagai anggota kelas atau berasal dari tuple. Jika tidak, yang Anda lakukan adalah lebih banyak pekerjaan. Ketika memutuskan di antara keduanya, pertanyaan penting untuk dijawab adalah: Apakah Anda ingin kelas Anda seperti itu tuple? Jika tidak, saya akan merekomendasikan berisi tuple dan membatasi antarmuka dengan menggunakan delegasi.

Anda dapat membuat pengakses untuk "mengganti nama" anggota tupel.

Lee Louviere
sumber
Saya membaca pertanyaan OP sebagai arti "apakah menerapkan kelas saya ' operator<menggunakan std::tiemasuk akal?" Saya tidak mengerti bagaimana jawaban ini berhubungan dengan pertanyaan itu.
ildjarn
@ildjarn Ada beberapa komentar yang tidak saya posting di sini. Saya telah mengumpulkan semuanya sehingga terbaca lebih baik.
Lee Louviere