Statik olmayan veri üyesinin ve iç içe sınıf oluşturucunun sınıf içi başlatması kullanılırken hata oluştu


90

Aşağıdaki kod oldukça önemsiz ve iyi derlenmesini bekliyordum.

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

Bu kodu g ++ sürüm 4.7.2, 4.8.1, clang ++ 3.2 ve 3.3 ile test ettim. Bu koddaki g ++ 4.7.2 segfaults'un ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770 ) dışında, test edilen diğer derleyiciler fazla açıklamayan hata mesajları veriyor.

g ++ 4.8.1:

test.cpp: In constructor ‘constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for ‘A::B::i’ has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method ‘constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

clang ++ 3.2 ve 3.3:

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

Bu kodu derlenebilir yapmak mümkündür ve hiçbir fark yaratmamalı gibi görünüyor. İki seçenek vardır:

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

veya

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

Bu kod gerçekten yanlış mı yoksa derleyiciler yanlış mı?


3
G ++ 4.7.3'üm internal compiler error: Segmentation faultbu koda diyor ki ...
Fred Foo

2
(hata C2864: 'A :: B :: i': bir sınıf içinde yalnızca statik sabit integral veri üyeleri başlatılabilir) VC2010'un söylediği şeydir. Bu çıktı g ++ ile uyumludur. Clang da bunu söylüyor, ancak daha az mantıklı geliyor. Bir yapıdaki bir değişkeni, int i = 0öyle olmadığı sürece yaparak öntanımlı yapamazsınız static const int i = 0.
Chris Cooper

@Borgleader: BTW İfadeyi bir kurucuya B()bir işlev çağrısı olarak düşünmenin cazibesinden kaçınırdım . Bir kurucuyu asla doğrudan "çağırmazsınız". Bunu geçici oluşturan özel bir sözdizimi olarak düşünün B... ve kurucu, takip eden mekanizmanın derinliklerinde, bu sürecin yalnızca bir parçası olarak çağrılır.
Orbit'te Hafiflik Yarışları

2
Hmm, Bbu işe yarayacak gibi görünen bir kurucu eklemek gcc 4.7.
Shafik Yaghmour

7
İlginç bir şekilde, A'nın kurucusunun tanımını A'dan çıkarmak da işe yarıyor gibi görünüyor (g ++ 4.7); "Varsayılan varsayılan kurucu" sınıf tanımının sonundan önce ... kullanılamaz ".
moonshadow

Yanıtlar:


84

Bu kod gerçekten yanlış mı yoksa derleyiciler yanlış mı?

Eh, hiçbiri. Bu, her iki - diyor standart bir hata var Aiçin başlatıcı ayrıştırılırken tamamlanmış sayılır B::ive bu B::B()(için başlatıcı kullanan B::i) tanımı dahilinde kullanılabilir A. Bu açıkça döngüsel. Bunu düşün:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

Bu bir çelişki vardır: B::B()örtük olan noexceptIFF A()atmaz ve A()IFF atmaz B::B()olduğu değil noexcept . Bu alanda bir dizi başka döngü ve çelişkiler var.

Bu, temel sorunlar 1360 ve 1397 tarafından izlenir . Özellikle 1397 numaralı ana sayıdaki bu nota dikkat edin:

Belki de bunu ele almanın en iyi yolu, statik olmayan bir veri üyesi başlatıcısının kendi sınıfının varsayılan yapıcısını kullanması için kötü biçimlendirmek olacaktır.

Bu sorunu çözmek için Clang'da uyguladığım kuralın özel bir durumu. Clang kuralı, bir sınıf için varsayılan bir varsayılan kurucunun, o sınıf için statik olmayan veri üyesi başlatıcıları ayrıştırılmadan önce kullanılamamasıdır. Bu nedenle Clang burada bir tanılama yayınlar:

    A(const B& _b = B())
                    ^

... çünkü Clang, varsayılan başlatıcıları çözümlemeden önce varsayılan bağımsız değişkenleri ayrıştırır ve bu varsayılan bağımsız değişken, B'ın varsayılan başlatıcılarının zaten çözümlenmiş olmasını gerektirir (örtük olarak tanımlamak için B::B()).


Bunu bildiğim iyi oldu. Ancak, kurucu aslında "statik olmayan veri üyesi başlatıcısı tarafından kullanılmadığından" hata mesajı hala yanıltıcıdır.
aschepler

Bunu, bu sorunla ilgili geçmiş bir deneyiminizden mi yoksa sadece standardı (ve kusurların listesini) dikkatlice okuyarak mı biliyordunuz? Ayrıca +1.
Cornstalks

Bu ayrıntılı cevap için +1. Öyleyse çıkış yolu ne olabilir? İç sınıflara bağlı olan dış sınıf üyelerinin ayrıştırılmasının, iç sınıflar tam olarak oluşana kadar ertelendiği bir tür "2 aşamalı sınıf ayrıştırma" mı?
TemplateRex

4
@aschepler Evet, buradaki teşhis pek iyi değil. Bunun için llvm.org/PR16550 dosyasını doldurdum.
Richard Smith

@Cornstalks Bu sorunu, Clang'da statik olmayan veri üyeleri için başlatıcıları uygularken keşfettim.
Richard Smith

0

Belkide sorun budur:

§12.1 5. Varsayılan olan ve silinmiş olarak tanımlanmayan bir varsayılan kurucu, sınıf türünde (1.8) bir nesne oluşturmak için odr- kullanıldığında (3.2) veya ilk bildiriminden sonra açıkça varsayılan olarak belirlendiğinde örtük olarak tanımlanır

Bu nedenle, varsayılan kurucu ilk bakıldığında oluşturulur, ancak arama başarısız olur çünkü A tam olarak tanımlanmamıştır ve bu nedenle A içindeki B bulunmayacaktır.


Bundan emin değilim "öyleyse". Açıkça B bbir sorun değildir ve içinde açık yöntemler / açıkça bildirilmiş bir kurucu bulmak Bbir sorun değildir. Bu nedenle, kodu bu nedenle yasadışı ilan etmeden önce , aramanın neden farklı şekilde ilerlemesi gerektiğine dair bazı tanımları görmek güzel olurdu, böylece sadece bu durumda " Biçeride Abulunmaz", diğerlerinde bulunmaz.
moonshadow

Standartta, sınıf tanımının sınıf içi başlatma sırasında, yuvalanmış sınıflar da dahil olmak üzere tamamlanmış olarak kabul edildiği sözcükler buldum. Konuyla alakalı görünmediği için referansı kaydetme zahmetine girmedim.
Mark B

@moonshadow: ifade örtük olarak varsayılan yapıcıların odr kullanıldığında tanımlandığını söylüyor. ilk bildirimden sonra açıkça tanımlanır. Ve B b bir kurucu çağırmaz, A'nın kurucusu B'nin yapıcısını çağırır
fscan

Sorun böyle bir şey olsaydı, ' =0dan kaldırırsanız kod yine de geçersiz olacaktır i = 0;. Ancak bu olmadan =0, kod geçerlidir ve B()tanımının içinde kullanmaktan şikayet eden tek bir derleyici bulamazsınız A.
aschepler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.