wake-up-neo.com

Unvollständiger Typ ist in einer Klasse nicht zulässig, in einer Klassenvorlage jedoch zulässig

Der folgende Code ist ungültig:

struct foo {
    struct bar;
    bar x;        // error: field x has incomplete type
    struct bar{ int value{42}; };
};

int main() { return foo{}.x.value; }

Dies ist ziemlich klar, da foo::bar an der Stelle, an der foo::x definiert ist, als unvollständig betrachtet wird.

Es scheint jedoch eine "Problemumgehung" zu geben, die dieselbe Klassendefinition gültig macht:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

int main() { return foo{}.x.value; }

Diese arbeitet mit allen großen Compilern . Ich habe drei Fragen dazu:

  1. Ist das tatsächlich gültiger C++ - Code oder nur eine Eigenart der Compiler?
  2. Wenn es sich um gültigen Code handelt, gibt es im C++ - Standard einen Absatz, der diese Ausnahme behandelt?
  3. Wenn es sich um gültigen Code handelt, warum wird die erste Version (ohne template) als ungültig betrachtet? Wenn der Compiler die zweite Option herausfinden kann, sehe ich keinen Grund, warum er die erste nicht ermitteln könnte.

Wenn ich eine explizite Spezialisierung für void hinzufüge:

template <typename = void>
struct foo_impl {};

template<>
struct foo_impl<void> {
    struct bar;
    bar x;        // error: field has incomplete type
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

int main() { return foo{}.x.value; } 

Es ist noch einmal schlägt fehl .

20
Goran Flegar

Die eigentliche Antwort könnte ¯\_ () _/¯ sein, aber es ist wahrscheinlich momentan in Ordnung, da Vorlagen magisch sind, aber es kann explizit nicht in Ordnung sein, wenn andere Kernprobleme anstehen.

Erstens ist das Hauptproblem natürlich [class.mem]/14 :

Nicht statische Datenelemente dürfen keine unvollständigen Typen haben. 

Aus diesem Grund ist Ihr Beispiel ohne Vorlage schlecht geformt. Gemäß [Temperaturpunkt]/4 :

Für eine Klassenvorlagenspezialisierung, eine Klassenmitgliedvorlagenspezialisierung oder eine Spezialisierung für einen Klassenmitglied einer Klassenvorlage, wenn die Spezialisierung implizit instanziiert wird, weil von einer anderen Vorlagenspezialisierung aus referenziert wird, wenn der Kontext, auf den sich die Spezialisierung bezieht, abhängig ist für einen Schablonenparameter und wenn die Spezialisierung nicht vor der Instantiierung der umgebenden Schablone instanziiert wird, der Zeitpunkt der Instantiierung befindet sich unmittelbar vor dem Instantiierungspunkt der umschließenden Schablone. Andernfalls steht der Zeitpunkt der Instantiierung für eine solche Spezialisierung unmittelbar vor der Deklaration oder Definition des Namespace-Bereichs, die sich auf die Spezialisierung bezieht.

Dies legt nahe, dass foo_impl<void>::barvorfoo_impl<void> instanziiert wird und daher an dem Punkt abgeschlossen ist, an dem das nicht statische Datenelement vom Typ bar instanziiert wird. Also vielleicht ist es okay.

, Kernsprachenthemen 1626 und 2335 befassen sich jedoch mit nicht exakt gleichem, aber immer noch recht ähnlichem Problem bezüglich Vollständigkeit und Vorlagen, und beide weisen auf das Wunschvorgehen hin Der Vorlagenfall ist konsistent mit dem Nichtvorlagenfall. 

Was bedeutet das alles in seiner Gesamtheit? Ich bin mir nicht sicher. 

6
Barry

Ich denke, dieses Beispiel wird ausdrücklich von erlaubt 

17.6.1.2 Member-Klassen von Klassenvorlagen [temp.mem.class]

1 Eine Member-Klasse einer Klassenvorlage kann außerhalb der Klassenvorlagendefinition definiert werden, in der sie deklariert ist . [Hinweis: Die Member-Klasse muss vor der ersten Verwendung definiert werden, für die eine Instantiierung erforderlich ist (17.8.1).

template<class T> struct A {
  class B;
};

A<int>::B* b1; // OK: requires A to be defined but not A::B
template<class T> class A<T>::B { };
A<int>::B b2; // OK: requires A::B to be defined

—Endnotiz]

Das sollte funktionieren auch gut:

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
};

template<typename T>
struct foo_impl<T>::bar{ int value{42}; };

using foo = foo_impl<>;

int main()
{
    return foo{}.x.value;
}
5
VTT

Weitere Details zur akzeptierten Antwort

Ich bin nicht sicher, dass die akzeptierte Antwort die richtige Erklärung ist, aber für den Moment die plausibelste. Aus dieser Antwort extrapolieren, hier die Antworten auf meine ursprünglichen Fragen:

  1. Ist das tatsächlich gültiger C++ - Code oder nur eine Eigenart der Compiler? [Es ist gültiger Code.]
  2. Wenn es sich um gültigen Code handelt, gibt es im C++ - Standard einen Absatz, der diese Ausnahme behandelt? [[Temperaturpunkt]/4]
  3. Wenn es sich um gültigen Code handelt, warum wird die erste Version (ohne template) als ungültig betrachtet? Wenn der Compiler die zweite Option herausfinden kann, sehe ich keinen Grund, warum er die erste nicht ermitteln könnte. [Weil C++ komisch ist - es behandelt Klassenvorlagen anders als Klassen (wahrscheinlich hätten Sie diese erraten können). ]

Einige weitere Erklärungen

Was scheint passiert

Beim Instanziieren von foo{} in main instanziiert der Compiler eine (implizite) Spezialisierung für foo_impl<void>. Diese Spezialisierung verweist auf foo_impl<void>::bar in Zeile 4 (bar x;). Der Kontext befindet sich in einer Vorlagendefinition, sodass er von einem Vorlagenparameter abhängig ist und die Spezialisierung foo_impl<void>::bar offensichtlich nicht zuvor instanziiert wurde. Daher sind alle Voraussetzungen für [temp.point]/4 erfüllt, und der Compiler generiert Folgendes Zwischencode (Pseudo-Code):

template <typename = void>
struct foo_impl {
    struct bar;
    bar x;        // no problems here
    struct bar{ int value{42}; };
};

using foo = foo_impl<>;

// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$     int value{42};
$ };
// implicit specialization of foo_impl<void> 
$ struct foo_impl<void> {
$     struct bar;
$     bar x;   // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }

Über Spezialisierung

Wie unter [Temp.spez.]/4 :

Eine Spezialisierung ist eine Klasse, eine Funktion oder ein Klassenmitglied, das entweder instanziiert oder explizit spezialisiert ist.

der Aufruf von foo{}.x.value in der ursprünglichen Implementierung mit Vorlagen gilt daher als Spezialisierung (dies war etwas Neues für mich).

Über die Version mit expliziter Spezialisierung

Die Version mit expliziter Spezialisierung wird nicht kompiliert, da es so aussieht, als wäre:

wenn der Kontext, auf den sich die Spezialisierung bezieht, von einem Vorlagenparameter abhängt

gilt nicht mehr, daher gilt die Regel aus [temp.point]/4 nicht.

0
Goran Flegar