Search code examples
c++enumsstdmapstatic-assert

Elegant way to ensure a std::map has a concrete size in compilation time


I was trying to ensure that a std::map has the same size as an enum class at compile time. Avoiding the use of macros, if possible.

I tried with static_assert, but reading Stack Overflow I concluded that it can't be done because std::map is "constructed" at runtime. Or at least it's what I understood. So I get this "expression must be a constant value" error.

Looking at the code must be clearer than my poor explanations:

// event_types.h

enum class EventTypes {
  InitSuccessfull,
  KeyPressed,
  StartedCleanup,
  FinishedCleanup,
  Exit,
  count
};

static const std::map<EventTypes, std::string> kEventTypesNames = {
  { EventTypes::InitSuccessfull,  "InitSuccessfull" },
  { EventTypes::KeyPressed,       "KeyPressed" },
  { EventTypes::StartedCleanup,   "StartedCleanup" },
  { EventTypes::FinishedCleanup,  "FinishedCleanup" },
  { EventTypes::Exit,             "Exit" }
};

// this doesn't work, "expression must have a constant value"(kEventTypesNames.size())
static_assert(kEventTypesNames.size() == static_cast<std::underlying_type<kuge::EventTypes>::type>(EventTypes::count));

// this neither works
const unsigned int map_size = kEventTypesNames.size();
static_assert(map_size == static_cast<std::underlying_type<kuge::EventTypes>::type>(EventTypes::count));

So, what I want is to ensure that the size of the map is the same as the enum count so I don't forget to add the event on both places.

Any idea on how to do it? Or maybe I should think of another (better) way of getting the events "stringified" that doesn't require a map?


Solution

  • You can store the data in a datatype that can be inspected at compile time, such as an array.

    static const std::map<EventTypes, std::string>::value_type kEventTypesNamesData[] = {
    //                      Note "value_type", here ^^^^^^^^^^
      { EventTypes::InitSuccessfull,  "InitSuccessfull" },
      { EventTypes::KeyPressed,       "KeyPressed" },
      { EventTypes::StartedCleanup,   "StartedCleanup" },
      { EventTypes::FinishedCleanup,  "FinishedCleanup" },
      { EventTypes::Exit,             "Exit" }
    };
    
    // Compile-time size check
    static_assert(end(kEventTypesNamesData)-begin(kEventTypesNamesData) == static_cast<std::underlying_type<EventTypes>::type>(EventTypes::count));
    
    // Construct from data
    static const std::map<EventTypes, std::string> kEventTypesNames( begin(kEventTypesNamesData), end(kEventTypesNamesData) );