确保 std::map 在编译时具有具体大小的优雅方法

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

我试图确保 std::map 在编译时与 enum class 具有相同的大小。尽可能避免使用宏。

我尝试使用 static_assert,但阅读 Stack Overflow 我得出的结论是无法完成,因为 std::map 是在运行时“构建”的。或者至少这是我的理解。所以我得到这个“表达式必须是一个常量值”错误。

看代码肯定比我拙劣的解释更清楚:

// 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));

所以,我想要的是确保地图的大小与 enum 计数相同,这样我就不会忘记在两个地方添加事件。

知道怎么做吗?或者也许我应该考虑另一种(更好的)方法来使不需要地图的事件“字符串化”?

使用辅助生成器,您可以:

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

您可以将数据存储在编译时可以检查的数据类型中,例如数组。

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) );

如果您愿意走“非代码”路线,您可以创建一个小型代码生成器来生成代码。然后只需将生成的代码包含在您的程序中(可能通过使用 #include "generated_code.hpp" 或类似的东西)。

这是一个小的:

#include <string>
#include <iostream>
#include <sstream>

// This would be in your data file of enums   
std::string test = "InitSuccessful\n"
                    "KeyPressed\n"
                    "StartedCleanup\n"
                    "FinishedCleanup\n"
                    "AnotherNewEnum1\n"
                    "AnotherNewEnum2\n"
                    "AnotherNewEnum3\n"
                    "Exit";

int main() {
    std::istringstream strmIn(test);
    std::ostringstream enumOut;
    std::ostringstream mapOut;
    std::string line;
    enumOut << "enum class EventTypes {\n";
    mapOut << "static const std::map<EventTypes, std::string> EventTypesNames = {\n";

    while (std::getline(strmIn, line))
    {
        enumOut << "    " << line << ",\n";
        std::string mapStr = "    { EventTypes::" + line + ",  \"" + line + "\" },";
        mapOut << mapStr << "\n";
    }
    enumOut << "    count\n};";
    mapOut << "};";

    // Output the generated source code
    std::cout << enumOut.str() << "\n\n\n";
    std::cout << mapOut.str() << "\n";
}

输出:

enum class EventTypes {
    InitSuccessful,
    KeyPressed,
    StartedCleanup,
    FinishedCleanup,
    AnotherNewEnum1,
    AnotherNewEnum2,
    AnotherNewEnum3,
    Exit,
    count
};


static const std::map<EventTypes, std::string> EventTypesNames = {
    { EventTypes::InitSuccessful,  "InitSuccessful" },
    { EventTypes::KeyPressed,  "KeyPressed" },
    { EventTypes::StartedCleanup,  "StartedCleanup" },
    { EventTypes::FinishedCleanup,  "FinishedCleanup" },
    { EventTypes::AnotherNewEnum1,  "AnotherNewEnum1" },
    { EventTypes::AnotherNewEnum2,  "AnotherNewEnum2" },
    { EventTypes::AnotherNewEnum3,  "AnotherNewEnum3" },
    { EventTypes::Exit,  "Exit" },
};