C ++ 20 davranışı mevcut kodu eşitlik operatörüyle bozuyor mu?

Jan 10 2021

Bu soruyu hata ayıklarken bununla karşılaştım .

Boost Operatörlerini kullanmak için onu tamamen kısalttım :

  1. 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.

  2. Örtük oluşturucuyu olarak değiştirdiğinizde, explicitsorunlu 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

81 Barry Jan 10 2021 at 07:27

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 == ykendisinin yalnızca bir adayı vardır: üye adayı ( #1) örtük yolarak 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: #2daha önce olduğu gibi üye olmayan aday ( ) ve şimdi de tersine çevrilmiş üye adayı ( #1tersine çevrilmiş). #2daha 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 ( #2tersine çevrilmiş). #2daha ö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 == xonun 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 == yvü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;
};