Are "Anonymous Structs" Standard? And, Really, What *Are* They

Are anonymous structs standard? And, really, what *are* they?

All the standard text refers to creating an "unnamed struct":

struct {
int hi;
int bye;
};

Just a nice friendly type, with no accessible name.

In a standard way, it could be instantiated as a member like this:

struct Foo {
struct {
int hi;
int bye;
} bar;
};

int main()
{
Foo f;
f.bar.hi = 3;
}

But an "anonymous struct" is subtly different — it's the combination of an "unnamed struct" and the fact that you magically get members out of it in the parent object:

struct Foo {
struct {
int hi;
int bye;
}; // <--- no member name!
};

int main()
{
Foo f;
f.hi = 3;
}

Converse to intuition, this does not merely create an unnamed struct that's nested witin Foo, but also automatically gives you an "anonymous member" of sorts which makes the members accessible within the parent object.

It is this functionality that is non-standard. GCC does support it, and so does Visual C++. Windows API headers make use of this feature by default, but you can specify that you don't want it by adding #define NONAMELESSUNION before including the Windows header files.

Compare with the standard functionality of "anonymous unions" which do a similar thing:

struct Foo {
union {
int hi;
int bye;
}; // <--- no member name!
};

int main()
{
Foo f;
f.hi = 3;
}

It appears that, though the term "unnamed" refers to the type (i.e. "the class" or "the struct") itself, the term "anonymous" refers instead to the actual instantiated member (using an older meaning of "the struct" that's closer to "an object of some structy type"). This was likely the root of your initial confusion.

Why does C++ disallow anonymous structs?

As others have pointed out anonymous unions are permitted in standard C++, but anonymous structs are not.

The reason for this is that C supports anonymous unions but not anonymous structs*, so C++ supports the former for compatibility but not the latter because it's not needed for compatibility.

Furthermore, there's not much use to anonymous structs in C++. The use you demonstrate, to have a struct containing three floats which can be referred to either by .v[i], or .x, .y, and .z, I believe results in undefined behavior in C++. C++ does not allow you to write to one member of a union, say .v[1], and then read from another member, say .y. Although code that does this is not uncommon it is not actually well defined.

C++'s facilities for user-defined types provide alternative solutions. For example:

struct vector3 {
float v[3];
float &operator[] (int i) { return v[i]; }
float &x() { return v[0]; }
float &y() { return v[1]; }
float &z() { return v[2]; }
};

* C11 apparently adds anonymous structs, so a future revision to C++ may add them.

Is there really an Anonymous class/struct in C++?

In the terminology of the C++ standard (N4659), only unions can be "anonymous". Neither the phrase "anonymous class" nor "anonymous struct" appear anywhere in the standard. In fact, the word "anonymous" itself appears only 44 times in the standard: 42 times followed by the word "union", and twice on its own underneath the "union" sublist of the Index.

When are anonymous structs and unions useful in C11?

Anonymous union inside structures are very useful in practice. Consider that you want to implement a discriminated sum type (or tagged union), an aggregate with a boolean and either a float or a char* (i.e. a string), depending upon the boolean flag. With C11 you should be able to code

typedef struct {
bool is_float;
union {
float f;
char* s;
};
} mychoice_t;

double as_float(mychoice_t* ch)
{
if (ch->is_float) return ch->f;
else return atof(ch->s);
}

With C99, you'll have to name the union, and code ch->u.f and ch->u.s which is less readable and more verbose.

Another way to implement some tagged union type is to use casts. The Ocaml runtime gives a lot of examples.

The SBCL implementation of Common Lisp does use some union to implement tagged union types. And GNU make also uses them.

Have anonymous structs and unions in C11 been incorrectly described?

TL;DR

This looks like a wording issue, they should not overlap.

Details

This is covered in Defect Report (DR) 499 which asks:

Given the following code:

union U {   
struct {
char B1;
char B2;
char B3;
char B4;
};
int word;
} u;

Does the storage of B1, B2, B3 and B4 overlap?

According to 6.7.2.1#13, the members should overlap in storage as they
become members of 'union U'.

At least one implementation (GCC) seems
to NOT consider them to be overlapping.

At least one implementation
(IBM's XL LE AIX) considers them to be overlapping as the standard
currently states.

And the committees response was:

The storage does not overlap.

A related issue is to be found in DR 502 and both may be resolved with coordinated wording changes.

and (emphasis added)

Change §6.7.2.1 p13 from:

An unnamed member of structure type with no tag is called an anonymous structure; an unnamed member of union type with no tag is
called an anonymous union. The members of an anonymous structure or
union are considered to be members of the containing structure or
union. This applies recursively if the containing structure or union
is also anonymous.

to:

An unnamed member of structure type with no tag is called an anonymous structure; an unnamed member of union type with no tag is
called an anonymous union. The names of members of an anonymous
structure or union are added to the name space of the containing
structure or union. This applies recursively if the containing
structure or union is also anonymous.

would adequately resolve the issue.

Is it legal to define an anonymous struct?

No, it is not allowed. GCC and Clang are right.

Per [dcl.dcl]/3 (7 Declarations) in N3337 (C++11 final draft), a class declaration must introduce at one name to the program. For example, the following are invalid:

enum { };
typedef class { };

(Note: this isn't unique to C++11. In N4140 (C++14 final draft) it is [dcl.dcl]/5 (7 Declarations). In N4659 (C++17 final draft) it is [dcl.dcl]/5 (10 Declarations).)

Are identically named members of unnamed structs in a union an error or a GCC bug?

It has been discussed in P.P.'s answer and the comments, that what you want isn't correct and GCC behaves correctly. But an easy workaround which fulfills your need might be that you rename a and b in the second struct:

union A {
struct {
int a:1;
int b:2;
int c1:29;
}PACKED;
struct {
int unused_name_a:1;
int unused_name_b:2;
int c2:28;
int d:1;
}PACKED;
int val;
}PACKED;

This worked for me in GCC and Clang and should still allow your trick to work nicely.

Rules for Anonymous Aggregates

As far as standards are concerned, anonymous structs are a C feature. They are not allowed by any C++ standard.

I could not find detailed gcc documentation about their extension to provide the feature in C++. What little I found is here, but that page appears to only describe the extension for C (prior to C11, this feature was not standard).

My question: If being a POD type is actually insufficient for being in an anonymous structure,

It indeed appears to be insufficient. The error message explains quite clearly that having a (non-trivial) constructor disqualifies a class from being an anonymous aggregate (structure). A POD would guarantee this only prior to C++11.

Since there appears to be little documentation for the extension, and since anonymous structs are a C feature, I'm tempted to guess that any such aggregate must not use C++ features. I believe that pre-C++11 definition of a POD satisfies such requirement.

A quick test appears to agree with my hypothesis. If you remove the constructor, the program compiles with the extension enabled. If you name the struct member (promoting the type to be unnamed), the program becomes well formed standard C++, and also compiles.

Or perhaps, since GCC is differing from all other compilers, is this a problem with GCC?

Since that's the way they implemented it, it quite possibly isn't a problem with them. It could be a problem to someone who wishes to compile without modification, a non-standard program written for another compiler. This is a problem with non-standard language features in general.



Related Topics



Leave a reply



Submit