Sean Bollin
Sean Bollin

Reputation: 910

Why is this union apparently holding more than one value?

I'm new to using unions and confused as to how this test is passing, SDL_Event being a union:

TEST(basic_check, test_eq) {
  Dot dot;

  SDL_Event event;            // this is a union, see below
  event.type = SDL_KEYDOWN;   // <= I use one member here

  SDL_Keysym keysym;          // this is a struct
  keysym.sym = SDLK_UP;
  event.key.keysym = keysym;  // <= I use another member here

  dot.handleEvent(event);     // <= but this function accesses value of the first member

  EXPECT_EQ(-Dot::DOT_VEL, dot.getVelY());
}

My understanding is that a union can ever only hold one value.

In this test however, I set a value into event.type, one member of the union; Then I update event.key, another member of the union. More precisely event.key is a struct and I update its member having a struct SDL_Keysym.

Here the code of the function that is then called:

void Dot::handleEvent(SDL_Event& e) {

  if (e.type == SDL_KEYDOWN && e.key.repeat == 0) { //<== access two alternate members?
    switch (e.key.keysym.sym) {   
      case SDLK_UP: 
        velY -= DOT_VEL;
        break;
      case SDLK_DOWN:
        ...   // followed by a lot of other cases 
    }
  }
}

I'm confused, because the if condition accesses two members of the union (see comment above). I thought they would be exclusive.

For information, SDL_Event and SDL_KeyboardEvent are defined like this:

typedef union SDL_Event
{
    Uint32 type;                    /**< Event type, shared with all events */
    SDL_CommonEvent common;         /**< Common event data */
    SDL_WindowEvent window;         /**< Window event data */
    SDL_KeyboardEvent key;          /**< Keyboard event data */
    ...            // and a long list of other events 
    ...
} SDL_Event;


typedef struct SDL_KeyboardEvent
{
    Uint32 type;        /**< ::SDL_KEYDOWN or ::SDL_KEYUP */
    Uint32 timestamp;
    Uint32 windowID;    /**< The window with keyboard focus, if any */
    Uint8 state;        /**< ::SDL_PRESSED or ::SDL_RELEASED */
    Uint8 repeat;       /**< Non-zero if this is a key repeat */
    Uint8 padding2;
    Uint8 padding3;
    SDL_Keysym keysym;  /**< The key that was pressed or released */
} SDL_KeyboardEvent;

Upvotes: 1

Views: 312

Answers (2)

Christophe
Christophe

Reputation: 73376

You are right about unions having at most one member active at any given time.

But the standard makes a guarantee, in order to facilitate the use of the union (and in particular to find out, like here, which is the active element):

9.5/1: (...) If a standard-layout union contains several standard-layout structs that share a common initial sequence, and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members;

In your example, the SDL_Event union has a union member Uint32 type and all the SDL_XXXEvent structures start as well with an Uint32. This is the common initial sequence, so that it can be inspected using any of the members (and the simplest one is just type).

Edit: interesting remark (taken over from the comments)

As you've pointed out, the test is not only inspecting: it also writes in type using the event.type and then assign the keysym in event.key. You therefore wonder if the change of active member (from type to key) wouldn't invalidate common initial sequence.

Be assured that this works perfectly. The C++ guarantee on inspection ensures that after the assignment of event.type (common initial sequence), event.key.type is also SDL_KEYDOWN. As you then change only event.key.keysim there is no reason that type's value changes.

Note however that timestamp, WindowsID and the other members of event.key are in an undefined state. Your test doesn't use them, so there is no reason to fail. But to avoid this kind of potential issues, a better approach could be to construct an SDL_KeyboardEvent, initialize it properly and copy the whole struct it into event.key

Upvotes: 4

Some programmer dude
Some programmer dude

Reputation: 409176

If you look closer at SDL_Event you will see that it's mostly a unio of structures, where each structure have the same initial signature (having a single 8-bit unsigned integer as the first member).

That is what makes it work. Even if the structures in the union will have different size, all will have the same member type as the first member.

That is a simple way to emulate inheritance, meaning that all the structures in the SDL_Event union to be siblings to each other.

Also, using a union to represent multiple types as a kind of type punning is explicitly allowed by the C specification.


However... Since the question is tagged as a C++ question, this is technically undefined behavior.

For backwards compatibility with C most (if not all) C++ compilers will allow this without any complaints.

Upvotes: 0

Related Questions