1

This is a language-lawyer follow up to this question, where all the answers agree that the following is not allowed:

struct A { A a; };

There are several instances where incomplete types can be used, within the type definition itself, e.g.

struct A { A *a; };
struct A { A &a; };
struct A { std::unique_ptr<A> a; };
struct A { std::vector<A> a; };     // since c++17

etc.

My question is, are compilers required to diagnose programs that use incomplete types in a way that is not allowed? i.e. is it ill-formed? Or do these programs have UB? Or something else?

My feeling is that certain cases are just UB, like the vector example, which compilers don't usually diagnose, even though it was only legalized in c++17.

To clarify, I'm only asking about using incomplete types within the definition of the type itself, like in the examples shown.

cigien
  • 50,328
  • 7
  • 37
  • 78
  • I think this is too broad -- there are dozens of places where incomplete types can be used; sometimes it is well-defined, sometimes ill-formed and sometimes UB/NDR – M.M Apr 22 '20 at 04:06
  • @M.M edited the question to clarify it. – cigien Apr 22 '20 at 04:14

1 Answers1

3

It depends on the exact rule. Violations of most rules related to incomplete class types in the core language section of the Standard must be diagnosed. Uses as a template argument to a Standard Library template are undefined behavior unless otherwise specified, which gives a bit more latitude to implementations.

These rules require diagnosis (since they are stated with "shall"):

[basic.def]/5:

In the definition of an object, the type of that object shall not be an incomplete type ([basic.types]), an abstract class type, or a (possibly multi-dimensional) array thereof.

[dcl.fct.def.general]/2:

The type of a parameter or the return type for a function definition shall not be a (possibly cv-qualified) class type that is incomplete or abstract within the function body unless the function is deleted ([dcl.fct.def.delete]).

[expr.ref]/4 (concerning the type of the operand expression before a . operator):

The class type shall be complete unless the class member access appears in the definition of that class. [ Note: If the class is incomplete, lookup in the complete class type is required to refer to the same declaration ([basic.scope.class]). — end note ]

Since the built-in meaning of the -> operator is defined with A->B equivalent to (*A).B, this also applies to -> operator expressions.

[class.mem]/15:

The type of a non-static data member shall not be an incomplete type ([basic.types]), an abstract class type ([class.abstract]), or a (possibly multi-dimensional) array thereof. [ Note: In particular, a class C cannot contain a non-static member of class C, but it can contain a pointer or reference to an object of class C. — end note ]

[class.derived]/2 (concerning the list of base classes for a class definition):

A class-or-decltype shall denote a (possibly cv-qualified) class type that is not an incompletely defined class ([class.mem]); any cv-qualifiers are ignored.

There are more core language rules forbidding incomplete class types, but the above are the most commonly relevant ones. See also the non-normative list of contexts requiring a complete class in [basic.def.odr]/12.

I don't see a direct rule that a qualified-id scope::name is ill-formed if scope names an incomplete class type, but it just may be that the name lookup will certainly fail in that case, which is a diagnosable violation.

For the Standard Library, the overall prohibition on incomplete types as template arguments is [res.on.functions]/(2.5):

In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ standard library depends on components supplied by a C++ program. If these components do not meet their requirements, this document places no requirements on the implementation.

In particular, the effects are undefined in the following cases:

  • ...

  • If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.

As noted, C++17 added specific permission to instantiate class std::vector<T, Alloc>, but none of its members, if T is incomplete and Alloc satisfies the "allocator completeness requirements" ([vector.overview]/4)

Community
  • 1
  • 1
aschepler
  • 65,919
  • 8
  • 93
  • 144