2

Suppose I am programming a game, designing a mechanism for input remapping on it. It has those two structures:

struct KeyboardSettings
{
    InputSource dashInput;
    InputSource jumpInput;
    InputSource bombInput;
    InputSource switchScreenLeft;
    InputSource switchScreenRight;
    InputSource pauseInput;

    InputSource moveUp;
    InputSource moveDown;
    InputSource moveLeft;
    InputSource moveRight;
};

struct JoystickSettings
{
    InputSource dashInput;
    InputSource jumpInput;
    InputSource bombInput;
    InputSource switchScreenLeft;
    InputSource switchScreenRight;
    InputSource pauseInput;

    InputSource movementAxisX;
    InputSource movementAxisY;
};

Now, I want my system do find out duplicates when reassigning a key, something akin to:

void UIInputRemappingButtonGroup::assignRemappingUniquely(InputSource& curSource, InputSource newSource)
{
    for (InputSource* orgSource : /* iterate through structure's members */)
        if (*orgSource == newSource) *orgSource = curSource;

    curSource = newSource;
}

Since I want this code to be as generic as possible I thought of treating both structures as if they were an "array" of InputSources, so the UIInputRemappingButtonGroup would receive an InputSource* and a size_t representing the array's beginning and size, and I would do something like this:

void UIInputRemappingButtonGroup::assignRemappingUniquely(InputSource& curSource, InputSource newSource)
{
    for (InputSource* orgSource = sourceCollection; orgSource != sourceCollection+sourceCollectionSize; ++orgSource)
        if (*orgSource == newSource) *orgSource = curSource;

    curSource = newSource;
}

Thus, if I was working on the screen to remap keyboard buttons, I would have a KeyboardSettings keyboardSettings and I would pass it to the UIInputRemappingButtonGroup like this:

buttonGroup.setInputSourceCollection(&keyboardSettings.dashInput, sizeof(keyboardSettings)/sizeof(InputSource));

And, similarly, if I had a JoystickSettings joystickSettings, I could just pass it to the UIInputRemappingButtonGroup using this code:

buttonGroup.setInputSourceCollection(&joystickSettings.dashInput, sizeof(joystickSettings)/sizeof(InputSource));

Problem is, I suspect this will trigger Undefined Behavior. Even though the two structures are standard-layout and trivial (and thus POD), I have not found anything on the standard that supports the validity of this technique or at least takes it out of the UB realm. Is it safe?

The InputSource is defined as this:

class InputSource final
{
private:
    enum class Type : uint8_t { Keyboard, MouseButton, MouseAxis, JoystickButton, JoystickAxis };

    Type type;
    size_t attribute;

    // Constructor private
    InputSource() = default;
    InputSource(Type type, uint32_t attr) : type(type), attribute(attr) {}
public:
    std::string getInputName(LocalizationManager& lm);

    static InputSource keyboardKey(size_t scanCode);

    static InputSource mouseButton(sf::Mouse::Button button);
    static InputSource mouseX, mouseY;
    static InputSource mouseWheel(sf::Mouse::Wheel wheel);

    static InputSource joystickButton(unsigned int button);
    static InputSource joystickAxis(sf::Joystick::Axis axis);

    friend bool operator==(InputSource in1, InputSource in2);
    friend bool operator<(InputSource in1, InputSource in2);

    friend bool readFromStream(sf::InputStream& stream, InputSource& in);
    friend bool writeToStream(OutputStream& stream, const InputSource& in);

    friend struct std::hash<InputSource>;
};

As seen, it satisfies the standard-layout requirements (all non-static data members having the same access control, no virtual functions or virtual base classes, no non-static data members of reference type, and all non-static data members standard-layout), and the trivial requirements (trivial constructor, copy operator and destructor), so it is also POD.

So, is this mechanism I devised safe from the standard's view?

JoaoBapt
  • 195
  • 1
  • 11
  • Try using pointer-to-member. – user202729 Mar 26 '18 at 01:05
  • @user202729 The problem is not setting a variable field from a same structure. It is traversing a structure made entirely from `InputSource`s. – JoaoBapt Mar 26 '18 at 01:07
  • 3
    Related: [struct member fields are not the same as an array](https://stackoverflow.com/questions/45332326/are-structs-of-variables-of-the-same-type-layout-compatible-with-a-struct-contai?rq=1) – Bo Persson Mar 26 '18 at 01:17

1 Answers1

2

When you would like to treat a structure as if it was an array, it often makes sense to use an actual array instead. This array will be safe to iterate.

struct KeyboardSettings
{
    enum input {
        dashInput,
        jumpInput,
        bombInput,
        switchScreenLeft,
        switchScreenRight,
        pauseInput,
        moveUp,
        moveDown,
        moveLeft,
        moveRight,

        count,
    };
    InputSource sources[count]; // you can use std::array if you prefer
};

Usage:

using i = KeyboardSettings::input;
KeyboardSettings s;
s.sources[i::moveUp];
// instead of
// s.moveUp;

This can be reduced to just s[i::moveUp] by overloading the subscript operator.


If you really would like to keep using members, there's the Boost Fusion library.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Interesting answer. I have thought about this months before when I was designing those particular classes, but I ended up going the member field way. Actually, the structure is treated as an array only for this particular code, the rest of the codebase uses them as if they were structs - always refering to them as `keyboardSettings. `. Maybe it would be better to refactor those other use cases. Anyway, thanks for the answer! – JoaoBapt Mar 26 '18 at 01:29