Why a nested class can't have a member the type of which is the one of the enclosing class?

StackOverflow https://stackoverflow.com/questions/20815669

  •  22-09-2022
  •  | 
  •  

Pergunta

One of the methods of a class C needs to return a struct containing a pair of integers and a fresh instance of C. It may look awkward, but given the overall design, this makes a lot of sense (think of a Waveform class returning a range of itself as a copy, with the indication of where the range starts and ends).

The problem is that this doesn't seem to be allowed. I can redesign my class in order to circumvent this issue but can you explain me why, from the point of view of the compiler, this cannot be done

struct S {
    struct S2 {
        S s;
    };
};

as S is an incomplete type (this is the compiler error), and instead this is perfectly fine

struct C {
    struct C1 {
        C makeC() { return C(); }
    };
};

Where is a substantial difference?

Foi útil?

Solução

At the point where you attempt to define S::S2, the type S is still an incomplete type, since its definition hasn't been completed yet. And class data members must have a complete type.

You can easily fix it like this:

struct S
{
    struct S2;   // declare only, don't define

    // ...
};

struct S::S2
{
    S s;         // now "S" is a complete type
};

It is essentially a design decision of C++ to not consider a type complete until the end of its definition. This prevents many pathological situations like the following example:

struct X
{
    struct Y { X a; };

    int b[sizeof(Y)];      // "sizeof" requires a complete type
};

Outras dicas

When you want to define a class you need a full definition of all the embedded (non-reference and non-pointer) members. While defining a class the class is not fully defined. That is, within a class definition the class under definition is merely declared, not defined.

That said, you can still have a member of your class in a nested class. You just need to define the nested class after the outer class is defined:

struct S {
    struct S2;
};

struct S::S2 {
    S s;
};

Since S is completely defined with the first closing braces, it can be used as embedded member after this point.

When you define a member function inside the class definition the definition is resolved as if it appears immediately after the class definition on the closest namespace level. That is, member function definitions know the fully defined class. The same doesn't apply to nested classes.

I don't know exactly why, but I could speculate on this counter-example:

struct S {
    struct S2 {
        S s;
    };
    S2 s2;
};

An attempt to scan S and determine its size before S2 is scanned will fail in this case. I suspect recursion may impose difficulties on the compiler and such difficulties are often reasons for rules in the standard.

That said, I think your solution is not very appropriate to the problem you have described. I'd make S2 a template on S, and not nested anyway. Then I would be able to use a reference S& for the template parameter, rather than S itself. So a range would be represented by the two integers and a reference to the original array object. Making a fresh copy from this range would be a separate operation.

The compiler cannot calculate size of the enclosed class.

You can write S* s; though.

This answer focuses on the difference between your two examples, as other answers already explain why the first doesn't work.

In a nutshell, your first example uses S within S in a way which requires S to be complete.

Your second example requires C to be complete as soon as the function body is being compiled. Your code is equivalent to

struct C {
    struct C1 {
        C makeC();
    };
};

inline C C::C1::makeC() {
    return C();
}

That means, the compiler "postpones" the function body automatically. For the function declaration, the compiler only needs to know that C is a type, but it works if C is incomplete, which it is to that point. When the function definition is being compiled, the type C is now complete.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top