I have a the following struct:
struct Foo
{
union
{
glm::vec2 size;
struct { float width, height; };
};
Foo() = default;
};
If I create an instance of Foo
with new
, I get the following error:
call to implicitly-deleted default constructor of 'Foo'
Note that I've read a few answers on SO, all are talking about using placement-new, but I did not fully understand how to apply that to my simple test-case.
CodePudding user response:
At most one non-static data member of a union may have a brace-or-equal-initializer. [Note: if any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. — end note ]
The relevant part is
if any non-static data member of a union has a non-trivial [member function] [...], [then] the corresponding member function of the union must be user-provided or it will be implicitly deleted.
So you will have to manually implement the constructor and destructor (and if you need them also the copy constructor, move constructor, copy assignment operator and move assignment operator).
The compiler can't provide a default constructor because there's no way it could know which of the union members should be initialized by the default constructor, or which union member the destructor needs to destruct.
So using the default constructor or trying to =default;
it won't work, you'll have to provide user-specified implementations.
A basic implementation could look like this:
struct Foo
{
bool isVector;
union
{
std::vector<float> vec;
struct { float width, height; };
};
// by default we construct a vector in the union
Foo() : vec(), isVector(true) {
}
void setStruct(float width, float height) {
if(isVector) {
vec.~vector();
isVector = false;
}
this->width = width;
this->height = height;
}
void setVector(std::vector<float> vec) {
if(!isVector) {
new (&this->vec) std::vector<float>();
isVector = true;
}
this->vec = vec;
}
~Foo() {
if(isVector) vec.~vector();
}
};
Note that you need to manually manage the lifetime of the union members, since the compiler won't do that for you.
In case the active union member is the struct
, but you want to activate the vector
member, you need to use placement new to construct a new vector in the union.
The same is true in the opposite direction: if you want to switch the active union member from the vector
to the struct
, you need to call the vector
destructor first.
We don't need to clean up the struct
, since it's trivial, so no need to call it's constructor / destructor.
If possible i would recommend using std::variant
or boost::variant
instead, since those do exactly that: they keep track of the active union member and call the required constructors / destructors as needed.