Warum wird der standardmäßige Standardkonstruktor für eine Union oder eine union-ähnliche Klasse gelöscht?

Dec 22 2020
struct A{
    A(){}
};
union C{
   A a;
   int b = 0;
};
int main(){
    C c;
}

Im obigen Code beschweren sich sowohl GCC als auch Clang , dass der Standardkonstruktor für union Cals gelöscht definiert ist.

Die relevante Regel besagt jedoch, dass:

Ein standardmäßiger Standardkonstruktor für Klasse X wird als gelöscht definiert, wenn:

  • X ist eine Union, die ein Variantenmitglied mit einem nicht trivialen Standardkonstruktor hat, und kein Variantenmitglied von X hat einen Standardelementinitialisierer .
  • X ist eine Nicht-Gewerkschaftsklasse mit einem Variantenmitglied M mit einem nicht trivialen Standardkonstruktor, und kein Variantenmitglied der anonymen Union mit M hat einen Standardelementinitialisierer .

Beachten Sie den hervorgehobenen Wortlaut. Im Beispiel IIUC sollte der bstandardmäßige Standardkonstruktor nicht als gelöscht definiert werden , da das Variantenmitglied über einen Standardelementinitialisierer verfügt. Warum melden diese Compiler diesen Code als schlecht geformt?

Wenn Sie die Definition von Cauf ändern

union C{
   A a{};
   int b;
};

Dann können alle Compiler diesen Code kompilieren. Das Verhalten deutet darauf hin, dass die Regel tatsächlich bedeutet:

X ist eine Vereinigung, die ein Variantenmitglied mit einem nicht trivialen Standardkonstruktor hat und für das Variantenmitglied kein Standardelementinitialisierer bereitgestellt wird

Ist dies ein Compiler-Fehler oder der vage Wortlaut dieser Regel?

Antworten

6 ecatmur Dec 22 2020 at 16:24

Dies wurde zwischen C ++ 14 und C ++ 17 über CWG 2084 geändert , wodurch die Sprache hinzugefügt wurde, mit der ein NSDMI für (ein beliebiges) Gewerkschaftsmitglied den standardmäßigen Standardkonstruktor wiederherstellen kann.

Das Beispiel zu CWG 2084 unterscheidet sich jedoch geringfügig von Ihrem:

struct S {
  S();
};
union U {
  S s{};
} u;

Hier befindet sich das NSDMI auf dem nicht trivialen Mitglied, während der für C ++ 17 angenommene Wortlaut es einem NSDMI auf jedem Mitglied ermöglicht, den standardmäßigen Standardkonstruktor wiederherzustellen. Dies liegt daran, dass, wie in dieser DR geschrieben,

Ein NSDMI ist im Grunde genommen syntaktischer Zucker für einen Mem-Initialisierer

Das heißt, das eingeschaltete NSDMI entspricht im int b = 0;Wesentlichen dem Schreiben eines Konstruktors mit mem-Initialisierer und leerem Körper:

C() : b{/*but use copy-initialization*/ 0} {}

Abgesehen davon ist die Regel, die sicherstellt, dass höchstens ein Mitglied der Gewerkschaft eine NSDMI hat, in einem Unterabschnitt von class.union.anon etwas verborgen :

4 - [...] Höchstens ein Variantenmitglied einer Gewerkschaft darf einen Standardmitgliedsinitialisierer haben.

Meine Vermutung wäre, dass sie, da gcc und Clang das oben Genannte bereits zulassen (das NSDMI für das nicht triviale Gewerkschaftsmitglied), nicht begriffen haben, dass sie ihre Implementierung ändern müssen, um vollständige C ++ 17-Unterstützung zu erhalten.

Dies wurde in der Liste der Standarddiskussionen im Jahr 2016 mit einem Beispiel besprochen, das Ihrem sehr ähnlich ist:

struct S {
    S();
};
union U {
    S s;
    int i = 1;
} u;

Die Schlussfolgerung war, dass clang und gcc bei der Ablehnung fehlerhaft sind, obwohl es zu der Zeit einen irreführenden Hinweis gab, der infolgedessen geändert wurde .

Für Clang ist der Fehler https://bugs.llvm.org/show_bug.cgi?id=39686Dies führt uns zurück zu SO bei implizit definiertem Konstruktor, der aufgrund des Variantenmitglieds N3690 / N4140 gegenüber N4659 / N4727 gelöscht wurde . Ich kann keinen entsprechenden Fehler für gcc finden.

Beachten Sie, dass MSVC richtig akzeptiert , und initialisiert czu .b = 0, was pro dcl.init.aggr richtig :

5 - [...] Wenn das Aggregat eine Union ist und die Initialisierungsliste leer ist, dann

  • 5.4 - Wenn ein Variantenmitglied einen Standardelementinitialisierer hat, wird dieses Mitglied von seinem Standardmitgliedsinitialisierer initialisiert. [...]
3 MichaëlRoy Dec 22 2020 at 17:30

Gewerkschaften sind eine knifflige Sache, da sich alle Mitglieder den gleichen Speicherplatz teilen. Ich stimme zu, der Wortlaut der Regel ist nicht klar genug, da er das Offensichtliche auslässt: Das Definieren von Standardwerten für mehr als ein Mitglied einer Gewerkschaft ist ein undefiniertes Verhalten oder sollte zu einem Compilerfehler führen.

Folgendes berücksichtigen:

union U {
    int a = 1;
    int b = 0;
};

//...
U u;                 // what's the value of u.a ? what's the value of u.b ? 
assert(u.a != u.b);  // knowing that this assert should always fail. 

Dies sollte natürlich nicht kompiliert werden.

Dieser Code wird kompiliert, da A keinen expliziten Standardkonstruktor hat.

struct A 
{
    int x;
};

union U 
{
    A a;        // this is fine, since you did not explicitly defined a
                // default constructor for A, the compiler can skip 
                // initializing a, even though A has an implicit default
                // constructor
    int b = 0;
};

U u; // note that this means that u.b is valid, while u.a has an 
     // undefined value.  There is nothing that enforces that 
     // any value contained by a struct A has any meaning when its 
     // memory content is mapped to an int.
     // consider this cast: int val = *reinterpret_cast<int*>(&u.a) 

Dieser Code kann nicht kompiliert werden, da A :: x einen expliziten Standardwert hat. Dies kollidiert mit dem expliziten Standardwert für U :: b (Wortspiel beabsichtigt).

struct A 
{
    int x = 1;
};

union U 
{
    A a;
    int b = 0;
};

//  Here the definition of U is equivalent to (on gcc and clang, but not for MSVC, for reasons only known to MS):
union U
{
    A a = A{1};
    int b = 0;
};
// which is ill-formed.

Dieser Code wird aus ungefähr dem gleichen Grund auch nicht auf gcc kompiliert, sondern auf MSVC (MSVC ist immer etwas weniger streng als gcc, daher ist es nicht überraschend):

struct A 
{
    A() {}
    int x;
};

union U 
{
    A a;
    int b = 0;
};

//  Here the definition of U is equivalent to:
union U
{
    A a = A{};  // gcc/clang only: you defined an explicit constructor, which MUST be called.
    int b = 0;
};
// which is ill-formed.

Wo der Fehler entweder am Deklarations- oder am Instanziierungspunkt gemeldet wird, hängt vom Compiler ab. Gcc und msvc melden den Fehler am Initialisierungspunkt, und clang meldet ihn, wenn Sie versuchen, die Vereinigung zu instanziieren.

Beachten Sie, dass es sehr nicht ratsam ist, Mitglieder einer Gewerkschaft zu haben, die nicht bitkompatibel oder zumindest bitrelatierbar sind. Dies unterbricht die Typensicherheit und ist eine offene Einladung für Fehler in Ihrem Programm. Typ Punning ist in Ordnung, aber für andere Anwendungsfälle sollte die Verwendung von std :: variante <> in Betracht gezogen werden.