c++struct

Incomplete type for a nested struct type declaration in C++ but not in C


If I compile this code with gcc as a .c file, everything is fine:

#include <stdio.h>

struct foo {
    struct bar {
        int baz;
    } bar;
};

int main() {
    struct bar a;
    a.baz = 2;
    printf("%d", a.baz);
    return 0;
}

If I rename it to .cpp, I get:

a.cpp: In function ‘int main()’:
a.cpp:10:20: error: aggregate ‘main()::bar a’ has incomplete type and cannot be defined
   10 |         struct bar a;
      |                    ^

I never knew such nested type declarations are even possible (and that they propagate to the global scope, which is unsurprising though). Yet I encountered an arcane macro which exploits this. Is it possible to make it compile when included into C++ code without making the struct type declaration unnested?


Solution

  • In C there is only one unscoped namespace for tags in which all tag names are located. There are no nested tag scopes. If you declare a nested struct with a tag, then that tag is also reachable from anywhere else in the program by its name.

    In C++ nested classes are scoped. A name declared in a class, even if it the name of another class, can only be looked up in the scope of the class.

    So, when you write struct bar in C, then that refers to the struct bar declared in foo. But in C++ it instead doesn't find any class of that name. The class inside foo would have to be named by qualified name struct foo::bar from main instead.

    Normally that would mean that the program is ill-formed because the name lookup for bar fails. However, because you use a so-called elaborated type qualifier, i.e. because you put struct before bar when naming it in main, the lookup failure instead causes the name bar to be implicitly declared in the surrounding namespace scope as a class. So you are actually referring to an, as of this point undefined, bar class in the global scope in main.

    You try to define a variable of this undefined, i.e. incomplete, type which is not allowed. The type of a variable definition must be complete, i.e. in the case of class types, the class must have a reachable definition, not only a reachable declaration.

    There is normally no point in prefixing struct to a class name in C++ when declaring a variable. That makes a difference in C where tags have their own separate namespace, but is pointless in C++ where this distinction does not exist and, as you see here, only causes confusion because it has this special implicit-declaration behavior.

    In your specific case it makes a difference because your class foo has a type member as well as a data member of name bar and the struct prefix causes lookup to ignore non-types.

    However, you already have unusual circumstances here that permit two different entities declared in the foo class scope to have the same name. That is in most cases not allowed to begin with (outside of function overloading).


    This is one of the intentional incompatibilities between ISO C and ISO C++ that have existed since the first standardization of C++. It is mentioned explicitly as such in [diff.class]/4 with rationale.

    It also suggests the following addition to your code to make it behave as in C:

    #include <stdio.h>
    
    // causes the elaborated type specifier `struct bar {/*...*/}`
    // in the data member definition in `foo` to find
    // this class at global scope instead of declaring
    // a new one in the `foo` scope.
    struct bar; 
    
    struct foo {
        struct bar {
            int baz;
        } bar;
    };
    
    int main() {
        struct bar a;
        a.baz = 2;
        printf("%d", a.baz);
        return 0;
    }
    

    Other such incompatibilities are listed in the linked section [diff.iso] of the standard (draft). (Note however that these are only the major differences and only those introduced at the time of standardization of C++98 relative to ISO C. Later changes in both languages added to the incompatibilities.)