Perilaku C ++ 20 melanggar kode yang ada dengan operator kesetaraan?
Saya mengalami ini saat men- debug pertanyaan ini .
Saya memangkasnya sampai hanya menggunakan Boost Operator :
Penjelajah Penyusun C ++ 17 C ++ 20
#include <boost/operators.hpp> struct F : boost::totally_ordered1<F, boost::totally_ordered2<F, int>> { /*implicit*/ F(int t_) : t(t_) {} bool operator==(F const& o) const { return t == o.t; } bool operator< (F const& o) const { return t < o.t; } private: int t; }; int main() { #pragma GCC diagnostic ignored "-Wunused" F { 42 } == F{ 42 }; // OKAY 42 == F{42}; // C++17 OK, C++20 infinite recursion F { 42 } == 42; // C++17 OK, C++20 infinite recursion }
Program ini mengkompilasi dan berjalan dengan baik dengan C ++ 17 (ubsan / asan diaktifkan) di GCC dan Clang.
Saat Anda mengubah konstruktor implisit menjadi
explicit
, baris bermasalah jelas tidak lagi dapat dikompilasi pada C ++ 17
Anehnya, kedua versi tersebut dikompilasi pada C ++ 20 ( v1 dan v2 ) , tetapi keduanya mengarah pada rekursi tak terbatas (crash atau loop ketat, bergantung pada level pengoptimalan) pada dua baris yang tidak dapat dikompilasi pada C ++ 17.
Jelas bug diam semacam ini yang merayap masuk dengan meningkatkan ke C ++ 20 mengkhawatirkan.
Pertanyaan:
- Apakah ini penyesuaian perilaku c ++ 20 (saya harap begitu)
- Apa sebenarnya yang mengganggu itu? Saya menduga itu mungkin karena dukungan baru "operator pesawat ruang angkasa" c ++ 20, tetapi tidak mengerti bagaimana hal itu mengubah perilaku kode ini.
Jawaban
Memang, C ++ 20 sayangnya membuat kode ini rekursif tanpa batas.
Berikut contoh yang dikurangi:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
// member: #1
bool operator==(F const& o) const { return t == o.t; }
// non-member: #2
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
Mari kita lihat 42 == F{42}
.
Di C ++ 17, kami hanya memiliki satu kandidat: kandidat non-anggota ( #2
), jadi kami memilih itu. Badannya,, x == y
sendiri hanya memiliki satu kandidat: kandidat anggota ( #1
) yang melibatkan konversi secara implisit y
menjadi sebuah F
. Dan kemudian calon anggota itu membandingkan dua anggota bilangan bulat dan ini benar-benar baik-baik saja.
Dalam C ++ 20, ekspresi awal 42 == F{42}
sekarang memiliki dua kandidat: kandidat non-anggota ( #2
) seperti sebelumnya dan sekarang juga kandidat anggota yang #1
dibalik ( dibalik). #2
lebih cocok - kami sama persis dengan kedua argumen alih-alih meminta konversi, jadi argumen itu dipilih.
Sekarang, bagaimanapun, x == y
sekarang memiliki dua calon: calon anggota lagi ( #1
), tetapi juga calon bukan anggota yang #2
dibalik ( dibalik). #2
adalah pencocokan yang lebih baik lagi karena alasan yang sama seperti sebelumnya: tidak perlu konversi. Jadi kami mengevaluasi y == x
. Rekursi tak terbatas.
Kandidat yang tidak mundur lebih disukai daripada kandidat yang mundur, tetapi hanya sebagai pemecah tiebreak. Urutan konversi yang lebih baik selalu yang pertama.
Oke bagus, bagaimana kita bisa memperbaikinya? Opsi paling sederhana adalah menghapus kandidat non-anggota seluruhnya:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
private:
int t;
};
42 == F{42}
di sini mengevaluasi sebagai F{42}.operator==(42)
, yang berfungsi dengan baik.
Jika kita ingin mempertahankan calon non-anggota, kita dapat menambahkan kandidat terbalik secara eksplisit:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
bool operator==(int i) const { return t == i; }
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
Hal ini membuat 42 == F{42}
tetap memilih calon bukan anggota, namun sekarang x == y
di badan tersebut akan lebih memilih calon anggota, yang kemudian melakukan persamaan normal.
Versi terakhir ini juga dapat menghapus kandidat non-anggota. Berikut ini juga berfungsi tanpa rekursi untuk semua kasus pengujian (dan begitulah cara saya menulis perbandingan di C ++ 20 ke depannya):
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
bool operator==(int i) const { return t == i; }
private:
int t;
};