Once you have a nontrivial type in your union
(so, one for which the language enforces "correct" initialization, i.e. constructor invocation), you have to write your constructors/destructors explicitly:
#include <SDL/SDL.h>
#include <glm/vec3.hpp>
#include <stdint.h>
#include <new>
#include <vector>
struct Event {
enum Type{
tRaw,
tAction,
tCursor,
} type;
struct Cursor {
glm::vec3 prevPos;
glm::vec3 pos;
};
union {
SDL_Event raw;
struct {
uint16_t actionID;
bool released;
} action;
Cursor cursor;
};
Event(const SDL_Event &raw) : type(tRaw) {
new(&this->raw) SDL_Event(raw);
}
Event(uint16_t actionID, bool released) : type(tAction) {
this->action.actionID = actionID;
this->action.released = released;
}
Event(glm::vec3 prevPos, glm::vec3 pos) : type(tCursor) {
new(&this->cursor) Cursor{prevPos, pos};
}
Event(const Event &rhs) : type(rhs.type) {
switch(type) {
case tRaw: new(&this->raw) SDL_Event(raw); break;
case tAction: memcpy((void *)&action, (const void *)&rhs.action, sizeof(action)); break;
case tCursor: new(&this->cursor) Cursor(rhs.cursor);
}
}
~Event() {
if(type == tCursor) {
this->cursor.~Cursor();
}
// in all other cases, no destructor is needed
}
};
int main() {
// Construction
Event ev(1, false);
SDL_Event foo;
Event ev2(foo);
glm::vec3 pos;
Event ev3(pos, pos);
// Copy construction & destruction
std::vector<Event> events;
events.push_back(ev);
events.push_back(ev2);
events.push_back(ev3);
events.clear();
return 0;
}
Some notes:
- I avoided the
data
member, opting instead for an anonymous union
; this avoids much boilerplate, as otherwise I'd have to write these constructors inside the union
(because it's the union
's constructor that is deleted and has to be explicitly defined), and then add forwarders on the outside; it also greatly simplifies the writing of the destructor (again, it would have to be written inside the union
, but the union
doesn't know the external type
; you can work around this, but it's tedious and verbose);
- I had to name explicitly the
Cursor
union, otherwise it's syntactically impossible to invoke its destructor (barring template tricks);
- I did not implement the assignment operator; it's not complicated, but honestly it's quite tedious. You can find a basic blueprint (check if same
type
; if same, do a regular assignment; otherwise, destroy active member and placement-new
onto the new one) in the link I posted before in the comments.
All this being said, this stuff has been already implemented in a more generic manner in C++17 as std::variant
, so, if you have a recent enough compiler, you may consider using it instead.
union
with a type which has a constructor. You must usestd::variant
(C++17) orboost::variant
in such case. – Thievenew
. – Debatableunion
, as it's ambiguous the constructor of what member would have to be invoked, so you have to write the constructor by yourself invoking explicitly (using the placementnew
syntax) the constructor of the one member you want to activate. – Debatableunion
constructor if yourunion
contains non-trivial types, that's the exact reason why the standard doesn't do this itself. The implicit contract for non-trivial types is that their constructor/destructor will always be invoked for an instance to be in a usable state, so you shouldn't really workaround it. That being said, I realize that forglm::vec3
it may be a venial sin - being it just a "box" of three scalars, I don't think there's much that can go wrong - the very fact that is non-trivial is probably accidental. – Debatable