• Advertisement

C++ POD (plain old data) type requirement in union

Recommended Posts

I'm sure many of you will have come across this situation before and am wondering the most elegant way of solving it:

This is an example, of wanting to create a struct/class that is an extension of a smaller version, and wanting to still be able to access the smaller version via a union. However the compiler seems to want the smaller struct to be a POD type (presumably because which constructors / destructors to call in a union is ambiguous). This is what I want, but it won't compile, complaining about Point2 being non-POD:

struct Point2
{
	Point2() {} // want this to be default
	Point2(int a, int b) {x = a; y = b;} // extra constructor for ease of use
	int x, y;
};

struct Point3
{
	union
	{
		struct
		{
			int x, y, z;
		};
		struct
		{
			Point2 xy; // access a Point3 as either a Point3 or a Point2
		};
	};
};

The only problem is it is sometimes very useful to be able to use the 2nd constructor when passing a Point2 as an argument to a function:

DrawLineTo(Point2(10, 20));

void DrawLineTo(const Point2 &pt)
{
	...
}

I am understanding there has been some changes to POD types in later c++ (I now have c++11 available), so is there any way of keeping it as a POD type while still having the secondary constructor available?

I can also see alternative methods of doing the same kind of thing (perhaps not using a union). What do you guys use?

Edited by lawnjelly

Share this post


Link to post
Share on other sites
Advertisement

Instead of giving Point2 an ease of use constructor that lets you write:
p = Point2(a, b);
These days in C++ you can not write any constructors at all and then just write:
p = Point2{a,b}
Or even just:
p = {a,b}
or
DrawLineTo({10, 20});

Share this post


Link to post
Share on other sites

Fantastico! Can't believe I didn't know that worked lol! :D

Edit - Just tested it and my gcc compiler does suggest 'extended initializer lists needs c++11 flag'. So it is available. Out of interest, what would have been a good solution prior to c++11?

I also just found this article:

http://cpp11standard.blogspot.co.uk/2012/11/c11-standard-explained-1-unrestricted.html

Which does suggest you can use non-POD type in a union in c++11, however in his example there is a need to name the union so you can then give it a constructor, which would seem to make the access needlessly complex, so it doesn't seem an ideal solution...

Point3 pt;
pt.myunion.xy.x = 1;

 

Edited by lawnjelly
tested

Share this post


Link to post
Share on other sites

Just tested the c++11 unrestricted union mentioned in the article, and it does work! Just one caveat the original code:

struct Point3
{
	union
	{
		struct
		{
			int x, y, z;
		};
		struct
		{
			Point2 xy; // access a Point3 as either a Point3 or a Point2
		};
	};
};

fails to compile with error: member 'Point2 Point3::<anonymous union>::<anonymous struct>::xy' with constructor not allowed in anonymous aggregate.

However this works:

struct Point3
{
	union
	{
		struct
		{
			int x, y, z;
		};
		Point2 xy;
	};
};

Hooray for c++11! :D
 

Share this post


Link to post
Share on other sites

With C++17 you'll also be able to use std::variant, which is basically a type-safe union type. Not sure if C++17 is an option for you, but I thought I'd mention it.

 

That doesn't allow type punning though.

Edited by rnlf_in_space

Share this post


Link to post
Share on other sites

Problem is not completely solved (except by Hodgman's method)..

This still causes a problem:

struct Rect
{
	union
	{
		struct
		{
			int x, y, w, h;
		};

		struct
		{
			Point2 pos;
			Point2 size;
		};
	};
};

Giving :

error: member 'Point2 Rect::<anonymous union>::<anonymous struct>::pos' with constructor not allowed in anonymous aggregate
    Point2 pos;
 

Whereas:

struct Rect
{
	union
	{
		struct
		{
			int x, y, w, h;
		};

		struct
		{
			Point2 pos;
			Point2 size;
		} ps;
	};
};

*does* compile, but it means you would have to refer to pos and size through ps.pos and ps.size.

The only other way of doing this that springs to mind is a bit more hacky:

struct Rect
{
	Point2 &pos() {return *((Point2 *)&x);}
	Point2 &size() {return *((Point2 *)&w);}

	int x, y, w, h;
};

 

Share this post


Link to post
Share on other sites

Clang will happily accept the first example with one small change:

struct Point2
{
    Point2() = default;
    Point2(int a, int b) {x = a; y = b;} // extra constructor for ease of use
    int x, y;
};
Adding the "=default;" in place of {} makes different C++11 rules apply to it and makes it a POD type.
 
To make gcc happy, you need to go one step further:
 
struct Point2
{
    Point2() = default;
    int x, y;
};
// Non-member function that works like a constructor
inline Point2 Make_Point2(int a, int b) {Point2 result; result.x = a; result.y = b; return result;}
I'm not sure if gcc is being overly strict here, or if Clang is being lenient in regards to the standard.

Share this post


Link to post
Share on other sites

Ah, thanks for the info on the different compilers guys! I'm using gcc on linux, and it has to compile on android compiler too and probably iOS and windows.

Come to think of it, if the goal is simply to reduce verbosity for access, then I could use an accessor to get to the union:

struct Rect
{
	Point2 &pos {return ps.pos;}
	Point2 &size {return ps.size;}
  
	union
	{
		struct
		{
			int x, y, w, h;
		};

		struct
		{
			Point2 pos;
			Point2 size;
		} ps;
	};
};

I have found this doc on unions:

http://en.cppreference.com/w/cpp/language/union

I had no idea about the conventions of calling all the constructors / destructors as the 'lifetime' of the members changes, sounds like a nightmare lol. It almost sounds like simply casting rather than using a union is the most explicit way to do it (treat the data as binary compatible with another struct), or sticking to POD and using Hodgman's technique.

Are there any gotchas (alignment etc) to avoiding the union altogether and simply casting data members to another type, out of interest?

i.e.

struct Rect
{
	Point2 &pos() {return *((Point2 *)&x);}
	Point2 &size() {return *((Point2 *)&w);}

	int x, y, w, h;
};

 

Share this post


Link to post
Share on other sites
3 hours ago, lawnjelly said:

Are there any gotchas (alignment etc) to avoiding the union altogether and simply casting data members to another type, out of interest?

Yes, even though there probably wouldn't be any alignment issues in this case*, there's also strict aliasing you have to keep in mind. Aliasing is when you have to two pointers pointing to the same location, with strict aliasing the compiler usually assumes that two pointers of different types never point to overlapping locations**. That's great for performance as the compiler is free to reorder access to those locations because it's assumed they don't overlap, but if you break the strict aliasing rule you can get undefined behaviour.

* Not 100% sure about this, consult an expert ^_^
** There are exceptions to this rule, like char* always aliases other types, here's a good intro to strict aliasing. Also, not an expert on this.

Share this post


Link to post
Share on other sites
20 hours ago, Mussi said:

Your first example compiles fine in VS2017, maybe you can use a different compiler?

Use W4 as warning level, then you'll notice that anonymous structs are non-standard C++. ;)

Standard C++ only supports anonymous unions due to C legacy.

Edited by matt77hias

Share this post


Link to post
Share on other sites
1 hour ago, matt77hias said:

Use W4 as warning level, then you'll notice that anonymous structs are non-standard C++.

Getting no warnings in VS2017, even with /Wall :-o. I do remember seeing warnings about anonymous structs in the past though, not sure what changed.

Share this post


Link to post
Share on other sites

Come to think of it, does this compile without any errors?

struct Rect
{
	union
	{
		struct
		{
			int x, y;
		};
		Point2 pos;
	};
	union
	{
		struct
		{
			int w, h;
		};
		Point2 size;
	};
};

No aggregates of anonymous structs, so might work?

Share this post


Link to post
Share on other sites
16 hours ago, lawnjelly said:

Are there any gotchas (alignment etc) to avoiding the union altogether and simply casting data members to another type, out of interest?

Yeah if any of the structures are using custom alignment (e.g. 16 byte alignment so that they will generate nice SSE instructions), then dodgy casting that causes the alignment to be incorrect may cause crashes.

In your case though, you're only using plaint ints which should all have the same alignment (probably 4B on a common compiler).

Share this post


Link to post
Share on other sites
12 hours ago, Mussi said:

Come to think of it, does this compile without any errors?


struct Rect
{
	union
	{
		struct
		{
			int x, y;
		};
		Point2 pos;
	};
	union
	{
		struct
		{
			int w, h;
		};
		Point2 size;
	};
};

No aggregates of anonymous structs, so might work?

It does! :) Ha good bit of lateral thinking lol. Just shows there is usually a way around these problems, but it always seems a bit of shoehorning a solution.

As matt77hias says, the anonymous struct doesn't seem to be technically allowed in c++, but lots of people use them.. and hence the different results on different compilers I guess.

Alignment sounds like it isn't likely to be a problem with casting, but aliasing bugs is a good call it sounds something to watch for.

Share this post


Link to post
Share on other sites

You cannot put things with constructors into unions, for the reason you state in the very top of your post: constructors/destructors become ambiguous. Which destructor, if any, should the compiler call when the union goes out of scope?

The work-around involves inheritance and constructors:

class Point2;

struct Point2Base {
    float x;
    float y;
    Point2 &asPoint() { return *(Point2*)this; }
};

class Point2 : public Point2Base {
public:
    // WARNING: Not explicit!
    Point2(Point2Base const &c) : Point2Base(c) {}
    void myFunction();
};

union MyUnion {
    struct {
        float x, y, z;
    } a;
    struct {
        Point2Base p;
    } b;
};

// OBSERVE: Value, not reference
void function_taking_point2(Point2 p2) {
}

int main() {
    MyUnion u;
    function_taking_point2(u.b.p);
    return 0;
}

 

VERY IMPORTANT though: If you pass u.b.p by reference, a temporary will be created, and you'll see the reference to the temporary in the called function, which is not why you want. This is why single-argument constructors should generally be made "explicit." That will break the convenience of passing values to functions, and you'll have to use asPoint() instead.

Here's a safer, slightly less convenient version:

class Point2;

struct Point2Base {
    float x;
    float y;
    Point2 &asPoint() { return *(Point2*)this; }
};

class Point2 : public Point2Base {
public:
    // GOOD: uses explicit constructor
    explicit Point2(Point2Base const &c) : Point2Base(c) {}
    void myFunction();
};

union MyUnion {
    struct {
        float x, y, z;
    } a;
    struct {
        Point2Base p;
    } b;
};

// Uses reference
void function_taking_point2(Point2 const &p2) {
}

int main() {
    MyUnion u;
    // Must use cast function
    function_taking_point2(u.b.p.asPoint());
    return 0;
}

 

Finally, note that the member function "asPoint()" doesn't disqualify Point2Base from being considered POD.

Constructors, destructors, and virtual member functions will disqualify a compound, but not plain member functions or static member functions.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Advertisement
  • Advertisement
  • Popular Now

  • Advertisement
  • Similar Content

    • By Snaked
      Im working in this project for 1 year .... mostly i develop a tool and databases for make the different maps and now i'm doing the client for play the game
      Tell me if you like it......
      this is a capture of how is viewing atm

       
       
      https://youtu.be/9251v4wDTQ0
    • By reenigne
      For those that don't know me. I am the individual who's two videos are listed here under setup for https://wiki.libsdl.org/Tutorials
      I also run grhmedia.com where I host the projects and code for the tutorials I have online.
      Recently, I received a notice from youtube they will be implementing their new policy in protecting video content as of which I won't be monetized till I meat there required number of viewers and views each month.

      Frankly, I'm pretty sick of youtube. I put up a video and someone else learns from it and puts up another video and because of the way youtube does their placement they end up with more views.
      Even guys that clearly post false information such as one individual who said GLEW 2.0 was broken because he didn't know how to compile it. He in short didn't know how to modify the script he used because he didn't understand make files and how the requirements of the compiler and library changes needed some different flags.

      At the end of the month when they implement this I will take down the content and host on my own server purely and it will be a paid system and or patreon. 

      I get my videos may be a bit dry, I generally figure people are there to learn how to do something and I rather not waste their time. 
      I used to also help people for free even those coming from the other videos. That won't be the case any more. I used to just take anyone emails and work with them my email is posted on the site.

      I don't expect to get the required number of subscribers in that time or increased views. Even if I did well it wouldn't take care of each reoccurring month.
      I figure this is simpler and I don't plan on putting some sort of exorbitant fee for a monthly subscription or the like.
      I was thinking on the lines of a few dollars 1,2, and 3 and the larger subscription gets you assistance with the content in the tutorials if needed that month.
      Maybe another fee if it is related but not directly in the content. 
      The fees would serve to cut down on the number of people who ask for help and maybe encourage some of the people to actually pay attention to what is said rather than do their own thing. That actually turns out to be 90% of the issues. I spent 6 hours helping one individual last week I must have asked him 20 times did you do exactly like I said in the video even pointed directly to the section. When he finally sent me a copy of the what he entered I knew then and there he had not. I circled it and I pointed out that wasn't what I said to do in the video. I didn't tell him what was wrong and how I knew that way he would go back and actually follow what it said to do. He then reported it worked. Yea, no kidding following directions works. But hey isn't alone and well its part of the learning process.

      So the point of this isn't to be a gripe session. I'm just looking for a bit of feed back. Do you think the fees are unreasonable?
      Should I keep the youtube channel and do just the fees with patreon or do you think locking the content to my site and require a subscription is an idea.

      I'm just looking at the fact it is unrealistic to think youtube/google will actually get stuff right or that youtube viewers will actually bother to start looking for more accurate videos. 
    • By mister345
      Hi, can someone please explain why this is giving an assertion EyePosition!=0 exception?
       
      _lightBufferVS->viewMatrix = DirectX::XMMatrixLookAtLH(XMLoadFloat3(&_lightBufferVS->position), XMLoadFloat3(&_lookAt), XMLoadFloat3(&up));
      It looks like DirectX doesnt want the 2nd parameter to be a zero vector in the assertion, but I passed in a zero vector with this exact same code in another program and it ran just fine. (Here is the version of the code that worked - note XMLoadFloat3(&m_lookAt) parameter value is (0,0,0) at runtime - I debugged it - but it throws no exceptions.
          m_viewMatrix = DirectX::XMMatrixLookAtLH(XMLoadFloat3(&m_position), XMLoadFloat3(&m_lookAt), XMLoadFloat3(&up)); Here is the repo for the broken code (See LightClass) https://github.com/mister51213/DirectX11Engine/blob/master/DirectX11Engine/LightClass.cpp
      and here is the repo with the alternative version of the code that is working with a value of (0,0,0) for the second parameter.
      https://github.com/mister51213/DX11Port_SoftShadows/blob/master/Engine/lightclass.cpp
    • By Rannion
      Hi, I am sending data to peers and those data need to be retreived from a scenegraph with a mutex to lock the data.
      The process of gathering the data is taking a bit less than a ms. I'm starting the thread every time I want to gather the data. If I'm running at 60 fps, I'm starting the thread 60 times per second so is that a performance or design problem?
      Would it be much better to have the thread always running and some kind of mechanism to ask him to perform the task whenever it's needed, so around 60 or 120fps?
      Also, does starting a thread creates some memory alloc/dealloc and then produce on the long run some kind of fragmentation?
      Thank you all.
    • By stream775
      Hello!
                I wrote a simple bones system that renders a 3D model with bones using software vertex processing. The model is loaded perfectly, but I can't see any colors on it. For illustration, you can see the 3D lines list, the bones ( 32 bones ) are in correct position ( bind pose ).
       
      Now, here's the problem. When I try to render the mesh with transformations applied then I see this:
      As you can see the 3D lines are disappearing, I'm guessing the model is rendered, but the colors are not visible for whatever reason. I tried moving my camera around the line list, but all I can see is some lines disappearing due to the black color of vertices? I'm not loading any textures, am I suppose to load them?
      However, if I render the vertices without applying ANY bone transformations, then I can see it, but it's a mess, obviously. If you're wondering why it's red, I have set color of these vertices ( only half of them ) to red and the rest half is white.
      First of all, my apologies for the messy code, but here it is:
      I'm not sure if vertices are suppose to have weights in them for software vertex processing. I'm storing them in a container, so you don't see them here.
      #define CUSTOMFVF ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE ) struct CUSTOMVERTEX { D3DXVECTOR3 Position; D3DXVECTOR3 Normal; DWORD Color; }; This is how I store the vertices in container and give them red and white color:
      This is how I create the device:
      For every frame:
      This is the UpdateSkinnedMesh method:
      I have debugged bone weights and bone indices. They are okay. Bone weights add up to 1.0f, so I'm really wondering why I can't see the model with colors on it?
  • Advertisement