C ++ 20 davranışı mevcut kodu eşitlik operatörüyle bozuyor mu?
Bu soruyu hata ayıklarken bununla karşılaştım .
Boost Operatörlerini kullanmak için onu tamamen kısalttım :
Derleyici Gezgini 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 }
Bu program hem GCC hem de Clang'da C ++ 17 (ubsan / asan etkin) ile derler ve sorunsuz çalışır.
Örtük oluşturucuyu olarak değiştirdiğinizde,
explicit
sorunlu satırlar açıkça artık C ++ 17'de derlenmez
Şaşırtıcı bir şekilde her iki sürüm de C ++ 20'de ( v1 ve v2 ) derlenir , ancak C ++ 17'de derlenmeyen iki satırda sonsuz özyinelemeye (optimizasyon seviyesine bağlı olarak çökme veya sıkı döngü ) yol açar .
Açıkçası, C ++ 20'ye yükselterek bu türden sessiz bir hata ortaya çıkması endişe vericidir.
Sorular:
- Bu c ++ 20 davranış uyumlu mu (öyle olmasını bekliyorum)
- Tam olarak ne müdahale ediyor? Bunun c ++ 20'nin yeni "uzay gemisi operatörü" desteğinden kaynaklandığından şüpheleniyorum, ancak bu kodun davranışını nasıl değiştirdiğini anlamıyorum .
Yanıtlar
Gerçekten de, C ++ 20 maalesef bu kodu sonsuz özyinelemeli yapar.
Aşağıda kısaltılmış bir örnek verilmiştir:
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;
};
Sadece bakalım 42 == F{42}
.
C ++ 17'de sadece bir adayımız vardı: üye olmayan aday ( #2
), bu yüzden onu seçiyoruz. Vücudunun x == y
kendisinin yalnızca bir adayı vardır: üye adayı ( #1
) örtük y
olarak bir F
. Ve sonra bu üye adayı iki tamsayı üyeyi karşılaştırır ve bu tamamen iyidir.
C ++ 20'de, ilk ifadenin 42 == F{42}
artık iki adayı vardır: #2
daha önce olduğu gibi üye olmayan aday ( ) ve şimdi de tersine çevrilmiş üye adayı ( #1
tersine çevrilmiş). #2
daha iyi eşleşme - bir dönüşümü çağırmak yerine her iki bağımsız değişkeni de tam olarak eşleştiriyoruz, bu nedenle seçildi.
Ancak x == y
şimdi artık iki aday var: tekrar üye aday ( #1
), ama aynı zamanda ters üye olmayan aday ( #2
tersine çevrilmiş). #2
daha önce daha iyi bir eşleşme olduğu için tekrar daha iyi eşleşmedir: dönüştürme gerekmez. Bu yüzden y == x
onun yerine değerlendiriyoruz . Sonsuz özyineleme.
Geri alınmayan adaylar, tersine çevrilen adaylara tercih edilir, ancak yalnızca eşitliği bozan adaylar olarak tercih edilir. Daha iyi dönüşüm sırası her zaman önce gelir.
Tamam harika, nasıl düzeltebiliriz? En basit seçenek, üye olmayan adayı tamamen kaldırmaktır:
struct F {
/*implicit*/ F(int t_) : t(t_) {}
bool operator==(F const& o) const { return t == o.t; }
private:
int t;
};
42 == F{42}
burada F{42}.operator==(42)
, hangisinin iyi çalıştığını değerlendirir .
Üye olmayan adayı elinde tutmak istiyorsak, geri alınmış adayını açıkça ekleyebiliriz:
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;
};
Bu 42 == F{42}
hala üye olmayan adayın seçilmesine neden olur , ancak şimdi x == y
vücutta üye adayı tercih edecek, o da normal eşitliği yapacak.
Bu son sürüm üye olmayan adayı da kaldırabilir. Aşağıdakiler de tüm test senaryoları için özyineleme olmadan çalışır (ve ileriye dönük olarak C ++ 20'de karşılaştırmaları nasıl yazacağım):
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;
};