Search code examples
c++enumsbit-manipulationunordered-mapbitflags

Is it possible to check for exactly n flags are set for an enum in C++ std::unordered_map key?


I am setting up an unordered_map, in which the key is an enum of Direction defined as:

enum Direction : uint16_t {
    North = 1 << 0,
    East = 1 << 2,
    South = 1 << 3,
    West = 1 << 4,
    None = 1 << 5,
};

The goal is to check if at least 2 of these flags are set, but up to n flags set.

With my enum defined above, I attempt to create a number of unordered_maps

    std::unordered_map<Direction, std::vector<Tile*>>*      groundTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      northEastHillTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      southEastHillTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      platformTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      wallTiles;

I initialize them as

    groundTiles = new std::unordered_map<Direction, std::vector<Tile*>>();
    groundTiles->insert_or_assign(Direction::None, std::vector<Tile*>());
    groundTiles->insert_or_assign(Direction::East, std::vector<Tile*>());
    groundTiles->insert_or_assign(Direction::West, std::vector<Tile*>());
    groundTiles->insert_or_assign((Direction)(Direction::East|Direction::West),std::vector<Tile*>());

and so on for the other sets

However, when I try to pull a Tile from the ground ( or any other set ) I cannot check if at least Direction::East and Direction::West are set. I have tried:

Tile* westAndEastCapped = southEastHillTiles->at((Direction)(Direction::West | Direction::East)).at(0);

But it seems to just default to the Direction::East set.

How can I select a tile from an unordered_map, with BOTH East and West flags set, and no others?


Solution

  • This is not how std::unordered_map::at() works. It only retrieves a value from the unordered map for a key that's equal to the passed in parameter. Equal as in "exactly". That's it. Nothing else.

    Additionally, at() throws an exception if the specified key does not exist.

    All that std::unordered_map knows about it's key is that it's some kind of an opaque value. std::unordered_map doesn't care about any interpretation or meaning of the key, except that the only thing that std::unordered_map requires from its key is that it has an equality comparison and that it has a hash function.

    An enum meets those requirements. The End. Just because this particular enum is interpreted as a bitmask, of some sorts, means nothing to a std::unordered_map.

    A std::unordered_map is not well-suited for the way you're trying to access its values, it's not going to work. You will likely need to design a custom container for your values that will implement the desired access pattern efficiently.

    In the worst case you can always resort to range iteration:

    for (auto &[key, value]: southEastHillTiles)
    {
      // ...
    }
    

    and then inspect each present key in the map to determine if it qualifies for what you're searching for. This is horribly inefficient, but that's pretty much the only thing that can be done with an unordered_map.