确保 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();
您可以将数据存储在编译时可以检查的数据类型中,例如数组。
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" },
};
我试图确保 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();
您可以将数据存储在编译时可以检查的数据类型中,例如数组。
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" },
};