Why can't I make a union containing a vec3 object?
Asked Answered
L

1

5

I can't seem to create a union where a member is or contains a glm::vec3 object (an object for representing a coordinate, containing 3 floats in this case). (source code for glm::vec)

It's used in the following code:

struct Event {
    enum Type{
        tRaw,
        tAction,
        tCursor,
    } type;
    union {
        SDL_Event raw;
        struct {
            uint16 actionID;
            bool released;
        } action;
        struct {
            glm::vec3 prevPos;
            glm::vec3 pos;
        } cursor; // offending object, compiles if this is removed
    } data;
};

Visual Studio gives me the following intellisense error.

"Error: the default constructor of "union Event::<unnamed>" cannot be referenced -- it is a deleted function"

If removed, the union compiles without any problems. What might be causing this problem, and is there anything I can do to remedy it?

Leatherman answered 29/5, 2018 at 6:16 Comment(9)
Please show the code that produces this error message.Sedgewinn
@Sedgewinn - added itLeatherman
You can't use a union with a type which has a constructor. You must use std::variant (C++17) or boost::variant in such case.Thieve
@Ext3h: false. It's possible since C++11, but you have to write explicitly a constructor that will do the relevant placement new.Debatable
stroustrup.com/C++11FAQ.html#unionsDebatable
@MatteoItalia - Maybe I'm misinterpreting it, but that says C++11 allows union members to have constructors for flexibility, but then follows by saying they're restricted by deleting the constructor (which apparently prohibits the type outright?) I'd be happy if the constructor is never called, that's what I'd want ideally.Leatherman
@AnneQuinn: it's a bit more complicated. If you have a nontrivial class with a constructor, the C++ language does not admit creating an instance without invoking the constructor. C++11 allows nontrivial types in unions, but it cannot generate the default constructor & co. in the containing union, 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 placement new syntax) the constructor of the one member you want to activate.Debatable
Thank you, I believe I understand now. I've given the union it's own type name and (empty) constructor, and it seems to work well now. it doesn't seem to require placement new though, I wonder if that'll come up later as a problem if I omit itLeatherman
@AnneQuinn: er... you shouldn't really have an empty union constructor if your union 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 for glm::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
D
7

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.

Debatable answered 29/5, 2018 at 8:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.