Anonymous and Unamed Struct - CPP
Anonymous and Unamed Struct - CPP
r/cpp
r/cpp
Posts
Search Reddit Open Gift
Posted by u/jube_dev 3 years ago
44
Anonymous and unamed struct
In the past, I wanted to do something like this:
struct Vector3 {
union {
struct {
float x;
float y;
float z;
};
float v[3];
};
};
It worked well, I was quite happy until I noticed a warning of the compiler saying that this was an
extension. More precisely, the standard says: "[Note: Nested types, anonymous unions, and functions
cannot be declared within an anonymous union. — end note]".
struct Shape {
ShapeType type;
union {
struct {
float radius;
} circle;
struct {
float width;
float height;
} rectangle;
};
};
Again, a warning. Note that the two examples differ a bit. In the latter, there is an unnamed struct , i.e.
the type has no name, but the object has. In the former, there is an "anonymous struct", i.e. the type has
no name and the object has no name (the standard use "anonymous union" with this same definition).
Also note that this code compiles fine on GCC, Clang and MSVC when extensions are active. So here we
have a very good example of a feature that should be in the standard and that is not, with previous
experience and implementation in many compilers. Why does it compile? Because, C11 introduced
anonymous struct . Here is the definition (6.7.2.1): "An unnamed member whose type specifier is a
structure specifier with no tag is called an anonymous structure; an unnamed member whose type
specifier is a union specifier 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."
So, here it comes. I would like to propose a paper to introduce anonymous structures and relax the
constraints of what can be put inside an anonymous union so that these two snippets could be standard. I
am not a native speaker, and this is the first time I write a paper. So I would like to find a mentor and/or a
co-author for this task, someone with more experience in the process. Anyone interested?
24 Comments
Award
Share
Comment as kinjalkishor
Comment
Comment
What are your thoughts?
Markdown Mode
manni66
· 3 yr. ago
And you want to write x,y,z into your Vector3 and then read the values as v[i]? Isn't that UB?
25
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
enchanted_mango_
· 3 yr. ago
It is UB (type punning), but it shouldn't be. It's kinda the point of their post I guess. It's just another thing
which is very, very useful in game programming but only "okay" because there are extensions for it.
36
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
jube_dev
OP · 3 yr. ago
In some case, it is not. The standard says: "[Note: One special guarantee is made in order to simplify
the use of unions: If a standard-layout union contains several standard-layout structs that share a
common initial sequence (12.2), and if a non-static data member of an object of this standard-layout
union type is active and is one of the standard-layout structs, it is permitted to inspect the common
initial sequence of any of the standard-layout struct members; see 12.2. — end note ]"
This condition may be relaxed if we consider that float v[3] has the same layout than a struct with
three floats. In that case, the code would not be UB.
13
Share
Share
Report
Report Save Follow
Follow
konanTheBarbar
· 3 yr. ago
I actually wanted to know this in detail once... while this is allowed by the C-standard, it is actually
still UB for the C++ standard. float v[3] and float,float,float do not share a common initial sequence
(by the wording of the c++ standard). While every compiler will just do the right thing, it is currently
still not covered by the standard.
26
Reply Give Award
Give
Share
Share
Report
Report Save
Save Follow
Follow
jbakamovic
· 3 yr. ago
Cxxd
5
Reply Give Award
Give Award
Share
Share
Report
Report Save
Save Follow
Follow
jube_dev
OP · 3 yr. ago
"The common initial sequence of two standard-layout struct types is the longest sequence of
non-static data members and bit-fields in declaration order,"
15
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
Supadoplex
· 3 yr. ago · edited 3 yr. ago
This condition may be relaxed if we consider that float v[3] has the same layout than a struct
with three floats. In that case, the code would not be UB.
Do you mean that standard could be relaxed, so that there would not be UB, or that there wouldn't
be UB if the layout is the same, given the current standard?
I don't think there's a guarantee for the latter unless the standard is changed.
3
Reply Give
Give Award
Share
Share
Report
Report Save
Save Follow
Follow
jube_dev
OP · 3 yr. ago
I agree with you, I meant the standard must be changed to guarantee this behavior.
3
Reply Give
Give Award
Award
Share
Share
Report
Report Save
Save Follow
Follow
jpan127
· 3 yr. ago
I forgot where I read it and someone correct me if I am wrong, but I think the standard defines it as UB
but major compilers like GCC implement it as defined and allowed behavior since it is so useful / widely
used.
11
Reply Give
Give Award
Award
Share
Report
Report Save
Save Follow
khedoros
· 3 yr. ago
That's the conclusion I came to last time I looked into it. The standard doesn't define the behavior, but
most compilers do. I use type-punning fairly often while doing game and emulator development.
1
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
ShillingAintEZ
· 3 yr. ago
You can always add an assert that the structure as a whole is 12 bytes. It is enormously useful and seems
to work with all major compilers.
0
Reply Give
Give Award
Award
Share
Supadoplex
· 3 yr. ago · edited 3 yr. ago
Again, a warning.
What compiler warned about the latter program? Unnamed (but indeed not anonymous) structs are standard
conformant as far as I know, so the warning seems uncalled for.
Edit:
Some compilers did support anonymous structs as an extension before C11 standardised them.
7
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
jube_dev
OP · 3 yr. ago
Clang says: "warning: anonymous types declared in an anonymous union are an extension [-Wnested-
anon-types]"
Unnamed (but indeed not anonymous) structs are standard conformant as far as I know, so the
warning seems uncalled for.
Per what I cited in the post, I think it is considered a nested type and is thus forbidden.
3
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
Supadoplex
· 3 yr. ago
Per what I cited in the post, I think it is considered a nested type and is thus forbidden.
You're citing a note. Notes shall not contain requirements and are not normative. The wording of the
note does seem to strongly imply that this is the case, but I would like to see the rule that actually says
so.
3
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
Deaod
· 3 yr. ago
The sentence immediately before that note is the following: "Each member-declaration in the
member-specification of an anonymous union shall either define a non-static data member or be a
static_assert-declaration."
Basically, the standard gives a whitelist of stuff you can define, and the note specifies what you can
not define.
The standard later goes on to explicitly list a few more things: "An anonymous union shall not have
private or protected members ([class.access]). An anonymous union shall not have member
functions."
3
Share
Share
Report
Report Save
Save Follow
Follow
Supadoplex
· 3 yr. ago
An unnamed struct definition does define a non-static data member though, so the wording
seems ambiguous to me. Perhaps that's why the compilers fail to agree as well.
1
Reply Give Award
Give Award
Share
Share
Report
Report Save
Save Follow
Follow
Deaod
· 3 yr. ago
It defines two things: A non-static data member of an unnamed struct type, and an unnamed
struct.
1
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
degski
· 3 yr. ago · edited 3 yr. ago
But on a serious note, yes, this should be un-UB'ed, certainly as C11 explicitly allows it [apparently].
PS: I think, generically, C++ should strive to be a super-set of C, this will keep everybody sane and there is no
reason whatsoever that what works in C cannot work in C++. In the worst case we should have an attribute [[
c_language ]], in a similar way as Rust has unsafe C++ bits. This then immediately makes all compliant C++
compilers C compliant, only MSVC needs some work.
4
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
tangerinelion
· 3 yr. ago
What's wrong with a class that defines x, y, z methods and operator[] and stores an array internally? I work on
CAD software and that's literally what we do. Force it to be inline if the function calls are too slow.
7
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
TheoreticalDumbass
· 3 yr. ago
vec.x() seems less pretty than vec.x, especially in vec.x() = 123 example.
6
Reply Give Award
Give Award
Share
Tyg13
· 3 yr. ago · edited 3 yr. ago
struct Vector {
Vector(float x, float y, float z) : v{x, y, z} {}
float v[3];
float& x = v[0];
float& y = v[1];
float& z = v[2];
};
1
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
TheoreticalDumbass
· 3 yr. ago
Your Vector is a lot larger than the author Vector3 struct, sizeof(Vector) = 40, while sizeof(Vector3) =
12.
1
Reply Give Award
Give
Share
Share
Report
Report Save
Save Follow
Follow
Tyg13
· 3 yr. ago
Ah, true.
1
Reply Give Award
Give Award
Share
Share
Report
Report Save
Save Follow
Follow
OrangeGirl_
· 3 yr. ago
To not get UB, this code provides the same interface at the cost of extra overhead:
struct Vector3 {
Vector3() : data(), x(data[0]), y(data[1]), z(data[2]) {}
float& operator[](std::size_t i)
{
return data[i];
}
const float& operator[](std::size_t i) const
{
return data[i];
}
float data[3];
float& x;
float& y;
float& z;
};
struct Shape {
Shape() : data(), radius(data[0]), width(data[0]), height(data[1]) {}
ShapeType type;
float data[2];
float& radius;
float& width;
float& height;
};
2
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
Nobody_1707
· 3 yr. ago
Can't you do the same thing without the overhead like so:
struct Vector3 {
float x, y, z;
Also, operator[] really should take a ptrdiff_t , since that's what it takes on actual arrays.
1
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
Supadoplex
· 3 yr. ago
The point of having an array is not so that you get operator[] . The need for the array is usually so
that you can have a pointer to x, and increment it to get y (without relying on UB).
3
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
Nobody_1707
· 3 yr. ago
Oh. Yeah, that makes sense. They really need to standardize on the way C11 handles this. That's
one of the few things that C got right that C++ didn't.
1
Reply Give
Give Award
Share
Share
Report
Report Save
Save Follow
Follow
remotion4d
· 3 yr. ago
https://github.com/google/mathfu/blob/master/include/mathfu/internal/vector_3.h#L184
2
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
sumo952
· 3 yr. ago
And isn't the same true for glm, which is quite widely used?
8
Reply Give
Give Award
Award
Share
Report
Report Save
Save Follow
remotion4d
· 3 yr. ago
The point is that there are a lot of libraries that do this and every compiler that I know support this.
2
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
e_man604
· 3 yr. ago
I even Saw this in a game dev Book and a guide for SIMD/SSE programming. I've been looking into it this
last week and just am confused.
What's the usecase of union if it's undefined behaviour? Or is it just because it's an inherited feature of c
and should be skipped in c++?
2
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
Supadoplex
· 3 yr. ago · edited 3 yr. ago
The purpose of union is to use a single area of memory for a (member) variable whose type is chosen
at runtime, but the choice is limited to a set of types. A typical use case is for example parsing a JSON
object: You cannot know at compile time whether the next element you read is going to be a string,
integer , array ... so when you parse the input into a data structure, you can specify a type that can hold
any one of those without allocating space for all possible types spearately.
For this use case, there is little use for union since C++17 added std::variant , which provides you
with safer, easier to use interface for the same thing. Having union in C++ is still important for
compatibility with C of course.
Trying to use union for type-punning or array indexing is undefined in standard C++. The C language -
unlike C++ - has been changed to allow these uses of union in C99 standard version. These may also
be supported by some C++ compilers as a language extension.
3
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
ShillingAintEZ
· 3 yr. ago
You can start with a union as the outside and put structs on the inside. You can then make one for x, y, z one
for float[3], one for r,g,b etc.
0
Reply Give Award
Share
Share
Report
Report Save Follow
Follow
jube_dev
OP · 3 yr. ago
If you mean:
union Vector3 {
struct {
float x;
float y;
float z;
};
struct {
float r;
float g;
float b;
};
float v[3];
};
struct Vector3 {
union { float x; float r; };
union { float y; float g; };
union { float z; float b; };
};
3
Reply Give Award
Give Award
Share
Report
Report Save
Save Follow
ShillingAintEZ
· 3 yr. ago