Reputation: 73294
Here's a simple code-pattern that keeps biting me in our codebase:
// in some_header_file.h
enum Color {
red = 0,
green,
blue,
max_item
};
// in some_other_file.cpp
static const char * color_names[max_item] = {
"red",
"green",
"blue"
};
std::string get_color_name(Color c) {
if ((c >= 0) && (c < max_item)) return color_names[c];
return "Unknown color";
}
... the above is all well and good, until one day, some careless programmer (okay, it's usually me) comes by and inserts a new color (e.g. yellow
) into the Colors
enum in some_header_file.h
(just before max_item
), but forgets to also add the new color's string (e.g. "yellow"
) to the end of the color_names[max_item]
array in some_other_file.cpp
.
After that happens, the code compiles fine with no errors or warnings, but future calls to get_color_name(yellow)
will (at best) not return the expected result or (at worst) invoke undefined behavior and maybe crash.
What I'd like is for this mistake to be caught at compile-time, so that I can avoid introducing runtime-error when updating enums. Is there a way in C++ to enforce at compile-time that the number of initializer-strings for color_names
must be equal to the array's length (i.e. to max_item
)?
Upvotes: 2
Views: 91
Reputation: 3869
This is an alternative to your problem, but may not be applicable if you don't have control to modify the source headers where the enums are defined.
The full answer here: How to easily map c++ enums to strings
My solution with macros
More complete/efficient code. But can handle up to 170 enum values, if you need more than 170 feel free to modify this code. This code generates extensions methods for manipulate enum.
Syntax
EZNUM_ENUM(EnumName,Var1,Oper1,Val1,Var2,Oper2,Val2,......,Var170,Oper170,Val170)
EZNUM_ENUM_UT(EnumName,UType,Var1,Oper1,Val1,Var2,Oper2,Val2,......,Var170,Oper170,Val170)
_
this value is ignored.this macro needs groups of 3 - Var,Oper,Val
examples:
X,_,_
it generates X
Y,EQ,2
| it generates Y = 2
Z,_,2
| it generates Z
For example
EZNUM_ENUM(MobaGame,
Dota2, EQ, 100,
LeagueOfLegends, EQ, 101,
HeroesOfTheStorm, EQ, 102,
Smite, EQ, 103,
Vainglory, EQ, 104,
ArenaOfValor, EQ, 105,
Paragon, EQ, 106,
HeroesOfNewerth, EQ, -100)
It generates
enum class MobaGame : int
{
Dota2 = 100,
LeagueOfLegends = 101,
HeroesOfTheStorm = 102,
Smite = 103,
Vainglory = 104,
ArenaOfValor = 105,
Paragon = 106,
HeroesOfNewerth = -100,
};
class MobaGameEnumExtensions
{
public:
[[nodiscard]] static String ToIntegralString(const MobaGame &value)
{
using namespace Extensions;
return EnumExtensions::ToIntegralString(value);
}
[[nodiscard]] static int ToIntegral(const MobaGame &value)
{
using namespace Extensions;
return EnumExtensions::ToIntegral<MobaGame>(value);
}
[[nodiscard]] static std::string ToString(const MobaGame &value, bool includeEnumName = false)
{
using namespace Extensions;
static const std::map<MobaGame, String> values = {
{MobaGame::Dota2, "Dota2"},
{MobaGame::LeagueOfLegends, "LeagueOfLegends"},
{MobaGame::HeroesOfTheStorm, "HeroesOfTheStorm"},
{MobaGame::Smite, "Smite"},
{MobaGame::Vainglory, "Vainglory"},
{MobaGame::ArenaOfValor, "ArenaOfValor"},
{MobaGame::Paragon, "Paragon"},
{MobaGame::HeroesOfNewerth, "HeroesOfNewerth"},
};
return includeEnumName ? "MobaGame::"s + values.at(value) : values.at(value);
}
[[nodiscard]] static MobaGame Parse(const int &value)
{
using namespace Exceptions;
static const std::map<int, MobaGame> values = {
{static_cast<int>(MobaGame::Dota2), MobaGame::Dota2},
{static_cast<int>(MobaGame::LeagueOfLegends), MobaGame::LeagueOfLegends},
{static_cast<int>(MobaGame::HeroesOfTheStorm), MobaGame::HeroesOfTheStorm},
{static_cast<int>(MobaGame::Smite), MobaGame::Smite},
{static_cast<int>(MobaGame::Vainglory), MobaGame::Vainglory},
{static_cast<int>(MobaGame::ArenaOfValor), MobaGame::ArenaOfValor},
{static_cast<int>(MobaGame::Paragon), MobaGame::Paragon},
{static_cast<int>(MobaGame::HeroesOfNewerth), MobaGame::HeroesOfNewerth},
};
try
{
return values.at(value);
}
catch (...)
{
throw ParseException("MobaGame::Parse"s);
}
}
[[nodiscard]] static MobaGame Parse(const String &value)
{
using namespace Exceptions;
using namespace Extensions;
static const std::map<String, MobaGame> values = {
{"Dota2", MobaGame::Dota2},
{"LeagueOfLegends", MobaGame::LeagueOfLegends},
{"HeroesOfTheStorm", MobaGame::HeroesOfTheStorm},
{"Smite", MobaGame::Smite},
{"Vainglory", MobaGame::Vainglory},
{"ArenaOfValor", MobaGame::ArenaOfValor},
{"Paragon", MobaGame::Paragon},
{"HeroesOfNewerth", MobaGame::HeroesOfNewerth},
{"MobaGame::Dota2"s, MobaGame::Dota2},
{"MobaGame::LeagueOfLegends"s, MobaGame::LeagueOfLegends},
{"MobaGame::HeroesOfTheStorm"s, MobaGame::HeroesOfTheStorm},
{"MobaGame::Smite"s, MobaGame::Smite},
{"MobaGame::Vainglory"s, MobaGame::Vainglory},
{"MobaGame::ArenaOfValor"s, MobaGame::ArenaOfValor},
{"MobaGame::Paragon"s, MobaGame::Paragon},
{"MobaGame::HeroesOfNewerth"s, MobaGame::HeroesOfNewerth},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::Dota2)), MobaGame::Dota2},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::LeagueOfLegends)), MobaGame::LeagueOfLegends},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::HeroesOfTheStorm)), MobaGame::HeroesOfTheStorm},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::Smite)), MobaGame::Smite},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::Vainglory)), MobaGame::Vainglory},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::ArenaOfValor)), MobaGame::ArenaOfValor},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::Paragon)), MobaGame::Paragon},
{IntegralExtensions::ToString(static_cast<int>(MobaGame::HeroesOfNewerth)), MobaGame::HeroesOfNewerth},
};
try
{
return values.at(value);
}
catch (...)
{
throw ParseException("MobaGame::Parse"s);
}
}
[[nodiscard]] static std::vector<MobaGame> GetValues()
{
return {
MobaGame::Dota2,
MobaGame::LeagueOfLegends,
MobaGame::HeroesOfTheStorm,
MobaGame::Smite,
MobaGame::Vainglory,
MobaGame::ArenaOfValor,
MobaGame::Paragon,
MobaGame::HeroesOfNewerth,
};
}
};
std::ostream &operator<<(std::ostream &os, const MobaGame &value)
{
os << MobaGameEnumExtensions::ToString(value);
return os;
}
Using this
EZNUM_ENUM_UT(MobaGame,int32_t
Dota2, EQ, 100,
LeagueOfLegends, EQ, 101,
HeroesOfTheStorm, EQ, 102,
Smite, EQ, 103,
Vainglory, EQ, 104,
ArenaOfValor, EQ, 105,
Paragon, _, _,
HeroesOfNewerth, _, _)
` It generates
enum class MobaGame : int32_t
{
Dota2 = 100,
LeagueOfLegends = 101,
HeroesOfTheStorm = 102,
Smite = 103,
Vainglory = 104,
ArenaOfValor = 105,
Paragon,
HeroesOfNewerth,
};
class MobaGameEnumExtensions
{
public:
[[nodiscard]] static String ToIntegralString(const MobaGame &value)
{
using namespace Extensions;
return EnumExtensions::ToIntegralString(value);
}
[[nodiscard]] static int32_t ToIntegral(const MobaGame &value)
{
using namespace Extensions;
return EnumExtensions::ToIntegral<MobaGame>(value);
}
[[nodiscard]] static std::string ToString(const MobaGame &value, bool includeEnumName = false)
{
using namespace Extensions;
static const std::map<MobaGame, String> values = {
{MobaGame::Dota2, "Dota2"},
{MobaGame::LeagueOfLegends, "LeagueOfLegends"},
{MobaGame::HeroesOfTheStorm, "HeroesOfTheStorm"},
{MobaGame::Smite, "Smite"},
{MobaGame::Vainglory, "Vainglory"},
{MobaGame::ArenaOfValor, "ArenaOfValor"},
{MobaGame::Paragon, "Paragon"},
{MobaGame::HeroesOfNewerth, "HeroesOfNewerth"},
};
return includeEnumName ? "MobaGame::"s + values.at(value) : values.at(value);
}
[[nodiscard]] static MobaGame Parse(const int32_t &value)
{
using namespace Exceptions;
static const std::map<int32_t, MobaGame> values = {
{static_cast<int32_t>(MobaGame::Dota2), MobaGame::Dota2},
{static_cast<int32_t>(MobaGame::LeagueOfLegends), MobaGame::LeagueOfLegends},
{static_cast<int32_t>(MobaGame::HeroesOfTheStorm), MobaGame::HeroesOfTheStorm},
{static_cast<int32_t>(MobaGame::Smite), MobaGame::Smite},
{static_cast<int32_t>(MobaGame::Vainglory), MobaGame::Vainglory},
{static_cast<int32_t>(MobaGame::ArenaOfValor), MobaGame::ArenaOfValor},
{static_cast<int32_t>(MobaGame::Paragon), MobaGame::Paragon},
{static_cast<int32_t>(MobaGame::HeroesOfNewerth), MobaGame::HeroesOfNewerth},
};
try
{
return values.at(value);
}
catch (...)
{
throw ParseException("MobaGame::Parse"s);
}
}
[[nodiscard]] static MobaGame Parse(const String &value)
{
using namespace Exceptions;
using namespace Extensions;
static const std::map<String, MobaGame> values = {
{"Dota2", MobaGame::Dota2},
{"LeagueOfLegends", MobaGame::LeagueOfLegends},
{"HeroesOfTheStorm", MobaGame::HeroesOfTheStorm},
{"Smite", MobaGame::Smite},
{"Vainglory", MobaGame::Vainglory},
{"ArenaOfValor", MobaGame::ArenaOfValor},
{"Paragon", MobaGame::Paragon},
{"HeroesOfNewerth", MobaGame::HeroesOfNewerth},
{"MobaGame::Dota2"s, MobaGame::Dota2},
{"MobaGame::LeagueOfLegends"s, MobaGame::LeagueOfLegends},
{"MobaGame::HeroesOfTheStorm"s, MobaGame::HeroesOfTheStorm},
{"MobaGame::Smite"s, MobaGame::Smite},
{"MobaGame::Vainglory"s, MobaGame::Vainglory},
{"MobaGame::ArenaOfValor"s, MobaGame::ArenaOfValor},
{"MobaGame::Paragon"s, MobaGame::Paragon},
{"MobaGame::HeroesOfNewerth"s, MobaGame::HeroesOfNewerth},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Dota2)), MobaGame::Dota2},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::LeagueOfLegends)), MobaGame::LeagueOfLegends},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::HeroesOfTheStorm)), MobaGame::HeroesOfTheStorm},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Smite)), MobaGame::Smite},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Vainglory)), MobaGame::Vainglory},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::ArenaOfValor)), MobaGame::ArenaOfValor},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Paragon)), MobaGame::Paragon},
{IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::HeroesOfNewerth)), MobaGame::HeroesOfNewerth},
};
try
{
return values.at(value);
}
catch (...)
{
throw ParseException("MobaGame::Parse"s);
}
}
[[nodiscard]] static std::vector<MobaGame> GetValues()
{
return {
MobaGame::Dota2,
MobaGame::LeagueOfLegends,
MobaGame::HeroesOfTheStorm,
MobaGame::Smite,
MobaGame::Vainglory,
MobaGame::ArenaOfValor,
MobaGame::Paragon,
MobaGame::HeroesOfNewerth,
};
}
};
std::ostream &operator<<(std::ostream &os, const MobaGame &value)
{
os << MobaGameEnumExtensions::ToString(value);
return os;
}
Full code
You can test/run/any this code from
It can't be pasted here. Too long code.
Here. The main program part
namespace Enums
{
EZNUM_ENUM_UT(Variables, int,
X, _, _,
Y, EQ, 25,
Z, EQ, 75)
EZNUM_ENUM_UT(Fruit, int32_t,
PEAR, EQ, -100,
APPLE, _, _,
BANANA, _, _,
ORANGE, EQ, 100,
MANGO, _, _,
STRAWBERRY, EQ, 75,
WATERMELON, EQ, 100)
EZNUM_ENUM(Animal,
Dog, _, _,
Cat, _, _,
Monkey, EQ, 50,
Fish, _, _,
Human, EQ, 100,
Duck, _, _,
__COUNT, _, _)
EZNUM_ENUM_UT(MathVars32, int32_t,
X, _, _,
Y, _, _,
Z, EQ, 75)
EZNUM_ENUM_UT(MathVars64, int64_t,
X, _, _,
Y, _, _,
Z, EQ, 75)
EZNUM_ENUM(Vowels,
A, EQ, 75,
E, _, _,
I, EQ, 1500,
O, EQ, -5,
U, _, _)
EZNUM_ENUM(MobaGame,
Dota2, EQ, 100,
LeagueOfLegends, EQ, 101,
HeroesOfTheStorm, EQ, 102,
Smite, EQ, 103,
Vainglory, EQ, 104,
ArenaOfValor, EQ, 105,
Paragon, EQ, 106,
HeroesOfNewerth, EQ, -100)
}
#define PRINT_VALUES(Name) std::cout << "EnumName: "s + #Name << std::endl; \
std::cout << StringExtensions::PadRight(EMPTY_STRING , 21 + 128, '_') << std::endl; \
for (Name element : Name##EnumExtensions::GetValues()) \
{ \
std::cout << StringExtensions::PadRight(Name##EnumExtensions::ToString(element), 16) << " | " << \
StringExtensions::PadRight(Name##EnumExtensions::ToString(element, true),32) << " | " << \
StringExtensions::PadRight(Name##EnumExtensions::ToIntegralString(element),8) << " | " << \
StringExtensions::PadRight(IntegralExtensions::ToString(Name##EnumExtensions::ToIntegral(element)),8) << " | " << \
StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToString(element))),16) << " | " << \
StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToString(element, true))),16) << " | " << \
StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToIntegralString(element))),16) << " | " << \
StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToIntegral(element))),16) << std::endl; \
} \
std::cout<< std::endl;
int main() {
using namespace Enums;
using namespace Extensions;
PRINT_VALUES(Variables)
PRINT_VALUES(Fruit)
PRINT_VALUES(Animal)
PRINT_VALUES(MathVars32)
PRINT_VALUES(MathVars64)
PRINT_VALUES(Vowels)
PRINT_VALUES(MobaGame)
/* std::cout << "EnumName: "s + "MobaGame" << std::endl;
std::cout << StringExtensions::PadRight(EMPTY_STRING, 21 + 128, '_') << std::endl;
for (MobaGame element : MobaGameEnumExtensions::GetValues())
{
std::cout << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(element), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(element, true), 32) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToIntegralString(element), 8) << " | " << StringExtensions::PadRight(IntegralExtensions::ToString(MobaGameEnumExtensions::ToIntegral(element)), 8) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToString(element))), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToString(element, true))), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToIntegralString(element))), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToIntegral(element))), 16) << std::endl;
}
std::cout << std::endl;*/
std::cin.get();
return 0;
}
Output
VisualC++
Upvotes: 2
Reputation: 218138
Alternatively, you might check that the zero value is not present in the array:
static constexpr const char * color_names[max_item] = {
"red",
"green",
"blue"
};
static_assert(std::ranges::find(color_names, nullptr) == std::end(color_names));
Upvotes: 2
Reputation: 117832
The easiest would probably be to remove max_item
from the array size and let it be automatically sized and then static_assert
on the size:
static const char* color_names[] = { // `max_item` removed here
"red",
"green",
"blue"
};
// and a static_assert added after the definition of the array:
static_assert(std::size(color_names) == max_item);
Upvotes: 6