How do I instantiate this flagged union? Compiler errors about remote constructor

Here's my tagged union:

struct UniformVariant
{
    enum class UNIFORM_TYPE {FLOAT, INT32, VEC2, VEC3, VEC4, MAT4} type;
    union
    {
        float f;
        int i;
        glm::vec2 v2;
        glm::vec3 v3;
        glm::vec4 v4;
        glm::mat4 m4;
    } value;
};

      

If I try to use it like this:

void some_function()
{
    UniformVariant v;
    some_other_function(v);
}

      

I am getting compile error use of deleted function 'UniformVariant::UniformVariant()'

It goes on to say that it was implicitly removed because the default definition would be ill-formed. So I tried adding a constructor toUniformVariant

UniformVariant() : value(0.0f), type(UNIFORM_TYPE::FLOAT) { };

      

But similar problems. I believe this has to do with the inclusion of class types in the union; but I cannot figure out the syntax to use this correctly.

+3


source to share


2 answers


From a note in [class.union]:

If any non-static data member from the union has a non-trivial default constructor (12.1), a copy constructor (12.8), a move constructor (12.8), a copy assignment operator (12.8), a move assignment operator (12.8), or a destructor (12.4) corresponding to a union member function must be provided by the user, or it will be implicitly removed (8.4.3) for a union.

One of the types in this union has a non-trivial default constructor, so you cannot create a default union. Consider this simpler reproduction:

struct X {
    X() { }  
};

struct Y {
    union {
        float f;
        X x;
    } value;
};

int main()
{
    Y y;
}

      

X

has a non-trivial default constructor, so it is Y::Y()

implicitly removed as the default constructor of anonymous connection is implicitly removed.



However, you can simply provide your own default constructor for this union:

union U {
    U() : f(0.f) { }

    float f;
    X x;
} value;

      

And now the example compiles.

However, if you are just implementing your own tagged union, I highly recommend using Boost.Variant . This is very useful and solves exactly this problem.

+6


source


The problem with your current join is that it doesn't specify which member should be initially active.

C ++ 11 has a feature called anonymous union in which no member is active initially and you activate each one as needed. This function is very important for implementing the Variant object that you are trying to do if there are no types with non-trivial constructors in the list.

However, your union is not anonymous as it has a named instance value

.

If a non-anonymous union contains any member with a non-trivial constructor (for example, your classes glm

), then this union must have a constructor that initializes exactly one of the members. (Even assuming there is an equal-equal-initializer element instead, it is not enough.)


Another option for you, besides Barry's excellent suggestion, is to switch to using anonymous join:

union
{
    float f;
    int i;
    glm::vec2 v2;
    glm::vec3 v3;
    glm::vec4 v4;
    glm::mat4 m4;
};
// ^^^^^ no name

      

When this happens, f

, i

, v2

, etc. "promoted" to names on the level UniformVariant

. Now you can write:



UniformVariant v;
v.i = 1;

      

To explain initialization again: when a struct contains an anonymous union, it doesn't actually initialize any of the members. You have to manually create and destroy the item when you use it.

For primitive types such as int

, you can simply activate them by assigning them, and no deactivation is required. But for class types, you must use place-new and destructor calls, for example:

new(&v2) glm::vec2(...args...);

      

and when you're done with this member:

v2.~vec2();

      

To avoid pitfalls, you probably want to do the union in the section private

and provide member functions X

to set the active member.

+1


source







All Articles