Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

C++ union question

Started by __fold Jun 27, 2005 at 6:50 AM 21 replies 6.7k views
Original Post
__fold
__fold
Is it legal c++ to use nameless structs and unions? I'm rewriting some old code and came up with this idea in a Vector3 class.
 
class Vector3 
{
public:
  union {
    float v[3]; //x, y, z
    struct { float x, y, z; };
  };

  //...
};


Vector3 vec;
// Then this is equal
vec.v[2] = 10.0f;
vec.z = 10.0f;

It compiles and works but I'm not sure I should use it. Is there an other way to do it?
Promit
Promit
No, it's not legal standard C++.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
DigitalDelusion
DigitalDelusion
anonymous unions are standard, anonymous struct's arent but they're supported as en extension on most compilers.
HardDrop - hard link shell extension."Tread softly because you tread on my dreams" - Yeats
Bincho
Bincho
Since it has been pointed out that it's not legal C++, here is a suggestion for an alternative: overload the array operator (not sure of it's correct name) so that 0, 1, and 2 return x, y, and z respectively.
Promit
Promit
You can also do a slightly peculiar trick like so:
class Vector{    public:        float &x, &y, &z        float v[3];        Vector() : x( v[0] ), y( v[1] ), z( v[2] )        {}};

This can come in handy if you want to template the size of the vector. Note that this will render the compiler unable to generate copy ctors or assignment operators, so you must write them yourself. There's no trick to writing these operators, just assign the references in ctors and never try to change them again.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
__fold
__fold
Quote:
Original post by Bincho
Since it has been pointed out that it's not legal C++, here is a suggestion for an alternative: overload the array operator (not sure of it's correct name) so that 0, 1, and 2 return x, y, and z respectively.


I also want the pointer. I've solved it before by overloading the cast operator. Then I could just pass a reference to a vector where the pointer to the data was needed. Very convenient and very ambiguous.

Quote:
Original post by Promit
You can also do a slightly peculiar trick like so:
*** Source Snippet Removed ***
This can come in handy if you want to template the size of the vector. Note that this will render the compiler unable to generate copy ctors or assignment operators, so you must write them yourself. There's no trick to writing these operators, just assign the references in ctors and never try to change them again.


Cool trick. Is there a way of removing x, y and z from the final build? Otherwise you have 100% memory overhead for each Vector3 object and it will break the alignment if you have an array of vectors.
Enigma
Enigma
Apologies if I've misread your question - I'm just about to rush off to lunch - but I think you want snk_kid's Slick Trick in C++™.

Enigma
Fruny
Fruny
Quote:
Original post by __fold
Cool trick. Is there a way of removing x, y and z from the final build? Otherwise you have 100% memory overhead for each Vector3 object and it will break the alignment if you have an array of vectors.


Try this:

#include <iostream>template<class T>class Vector3{public:   Vector3(const T &xx, const T &yy, const T &zz) :      x(xx), y(yy), z(zz)   {   };   T& operator[](size_t idx)   {      return this->*offsets_[idx];   };   const T& operator[](size_t idx) const   {      return this->*offsets_[idx];   };public:   T x,y,z;private:   static T Vector3::* const offsets_[3];};template<class T>T Vector3<T>::* const Vector3<T>::offsets_[3] ={   &Vector3<T>::x,   &Vector3<T>::y,   &Vector3<T>::z};int main(){   Vector3<float> vec(1,2,3);   vec[0] = 5;   std::cout << vec.x << std::endl;}


The memory overhead is reduced to one array per class. The code generated is identical to a direct member access when the index is known at compile time.

Discussion here.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
rick_appleton
rick_appleton
A very interesting piece of code.

However I have a question about the static array, and I have a feeling I'm not going to like the answer.

Will that static array be compiled out entirely or will there indeed be a global variable?

I'm wondering about this, because things like Brew don't currently support global data, which would make this solution unusable on such a system.
__fold
__fold
Thanks for all the answers!

The only drawback I can see with the solution presented by Fruny is that i don't understand it to 100%. :) I better let it sink in for a while.

I understand that it uses an array of type T Vector3::* which I assume is a pointer to a member of type T in the Vector3 class. Actually, now I think I understand it. :)
Fruny
Fruny
Rick - you're right, you're probably not going to like it. There probably are some workarounds, but they likely will add overhead (i.e. won't compile away to nothing).

__fold - entirely correct. One very important thing to keep in mind is that pointers to members and 'ordinary' pointers do not mix. If one is expected, you can't pass the other.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
snk_kid
snk_kid
Quote:
Original post by rick_appleton
I'm wondering about this, because things like Brew don't currently support global data, which would make this solution unusable on such a system.


The other alternative is:

#include <cstddef>struct vector3f {	   typedef std::size_t size_type;   float x, y, z;   // ...      const float& operator[](size_type index) const {      return reinterpret_cast<const float (&)[3]>(*this)[index]; // or return (&x)[index]; i'd prefer the former   }   float& operator[](size_type index) {      return reinterpret_cast<float (&)[3]>(*this)[index]; // or return (&x)[index]; i'd prefer the former   }};


Of-course that can have issues with alignment but highly unlikely.
rick_appleton
rick_appleton
Quote:
Original post by snk_kid
#include <cstddef>struct vector3f {	   typedef std::size_t size_type;   float x, y, z;   // ...      const float& operator[](size_type index) const {      return reinterpret_cast<const float (&)[3]>(*this)[index];   }   float& operator[](size_type index) {      return reinterpret_cast<float (&)[3]>(*this)[index];   }};


To be honest, some of the original code and the discussion in the other thread, went a bit over my head, so the following question might not be relevant. But why is your code snippet better than simply using a switch statement in the overload? I can only assume that the switch statement takes more cycles than your snippet?
Fruny
Fruny
Quote:
Original post by rick_appleton
I can only assume that the switch statement takes more cycles than your snippet?


Yes. When the index is known at compile time, foo[0] and foo.x produce the same code - direct access to the member variable. Given the restrictions of your platform (Brew) and the fact that this is really only syntactic sugar, I wouldn't bother with it. Just pick one or the other and that's it.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
__fold
__fold
If I do this instead.

T& operator[](size_t idx){   return *(&x + idx);}const T& operator[](size_t idx) const{   return *(&x + idx);}


Can't this also be optimized away at compile time for the same cases as the other code?
DigitalDelusion
DigitalDelusion
Quote:
Original post by __fold
If I do this instead.

*** Source Snippet Removed ***

Can't this also be optimized away at compile time for the same cases as the other code?


Probably, but it isn't portable since you can't assume much about the actual object layout.
HardDrop - hard link shell extension."Tread softly because you tread on my dreams" - Yeats
snk_kid
snk_kid
Quote:
Original post by __fold
T& operator[](size_t idx){   return *(&x + idx);}const T& operator[](size_t idx) const{   return *(&x + idx);}


Can't this also be optimized away at compile time for the same cases as the other code?


Is the same as:

return (&x)[index];


And has potentially the same alignment issues, the advantage of pointer to data members is it will not have those issues.

If you not going to use pointer to data members i would prefer this form:

return reinterpret_cast<const float (&)[3]>(*this)[index];//...return reinterpret_cast<float (&)[3]>(*this)[index];


If your unsure what the type is its a reference to an array. I would prefer this form because it explicitly states your intent and its potentially unsafe-ness is easily seen. Plus your not decaying to pointers not that it probably make any difference in this context though.

[Edited by - snk_kid on June 27, 2005 6:32:35 PM]
__fold
__fold
DigitalDelusion: When I use an array of Vector3 objects I need to know the layout of the objects otherwise I will loose too much performance.

snk_kid: That code makes a bit more sense. I think I go with the pointers to the members anyway. It's just that I never heard of it before today and it almost looks like it's too good to be true.
Shannon Barber
Shannon Barber
First nameless struct's are a very popular extension, I use them often with embedded code.

Second, you can return an indexed reference to x; It’s guaranteed to work so-long-as the class is a PoD and will in-fact always work less severely pathological cases, which you won't code when make a simple tuple class. Although it's no longer guaranteed to work once you add ctor’s/dtor’s and/or virtual functions, I've never encountered an implementations where it fails.

Fruny's code is always gauranteed to work, even in the pathological cases.

const T& operator[](size_t i) const
{
return (&this->x);
}

T& operator[](size_t i)
{
return (&this->x);
}
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Chris81
Chris81
Actually, if you look in the directx headers, you'll see Microsoft using this same union trick. I'm at work with no directx sdk, so I can't remember where and exactly what it looked like, but I think it went something like this:

struct Vector3 {  union {    float v[3]; //x, y, z    float x, y, z;  };  //...};


I think it's in dx9datatypes.h or something like that.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.