Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 636
Alex CB Avatar asked Oct 19 '25 14:10

Alex CB


2 Answers

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) );
like image 90
Drew Dormann Avatar answered Oct 22 '25 04:10

Drew Dormann


With helper generator, you might do:

std::map<EventTypes, std::string> MakeMap()
{ 
    constexpr std::pair<EventTypes, const char*> ini[]
    {
      { EventTypes::InitSuccessfull,  "InitSuccessfull" },
      { EventTypes::KeyPressed,       "KeyPressed" },
      { EventTypes::StartedCleanup,   "StartedCleanup" },
      { EventTypes::FinishedCleanup,  "FinishedCleanup" },
      { EventTypes::Exit,             "Exit" }
    };
    static_assert(std::size_t(EventTypes::count) == std::size(ini));
    return {std::begin(ini), std::end(ini)};
}
static const std::map<EventTypes, std::string> kEventTypesNames = MakeMap();

Demo

like image 26
Jarod42 Avatar answered Oct 22 '25 04:10

Jarod42



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!