如何在编译时检测缺少的字符串数组项初始化器?

How to detect a missing string-array-item-initializer at compile-time?

提问人:Jeremy Friesner 提问时间:7/17/2023 更新时间:7/17/2023 访问量:80

问:

这里有一个简单的 Code Pattern,它在我们的代码库中一直困扰着我:

// 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";
}

...以上一切都很好,直到有一天,一些粗心的程序员(好吧,通常是我)过来,在枚举中插入了新颜色(例如)(就在之前),但忘记将新颜色的字符串(例如)添加到数组的末尾。yellowColorssome_header_file.hmax_item"yellow"color_names[max_item]some_other_file.cpp

发生这种情况后,代码编译良好,没有错误或警告,但将来调用将(充其量)不会返回预期结果,或者(最坏情况)调用未定义的行为并可能崩溃。get_color_name(yellow)

我希望在编译时捕获此错误,以便避免在更新枚举时引入运行时错误。在 C++ 中是否有一种方法可以在编译时强制执行初始值设定项字符串的数量必须等于数组的长度(即 )?color_namesmax_item

C++ 数组枚 举静态 初始化

评论

0赞 Ripi2 7/17/2023
使用 a 而不是旧数组。然后用代替 .'at()' 成员将检查边界。std::vector<std::string>vector.at(pos)vector[pos]
0赞 Ted Lyngmo 7/17/2023
@ildjarn是的,这也是我的建议。
0赞 n. m. could be an AI 7/17/2023
只需使用魔术枚举即可。

答:

6赞 Ted Lyngmo 7/17/2023 #1

最简单的方法可能是从数组大小中删除,让它自动调整大小,然后调整大小:max_itemstatic_assert

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);
2赞 Jarod42 7/17/2023 #2

或者,您可以检查数组中是否不存在零值:

static constexpr const char * color_names[max_item] = {
   "red",
   "green",
   "blue"
};

static_assert(std::ranges::find(color_names, nullptr) == std::end(color_names));

演示

2赞 Joma 7/17/2023 #3

这是问题的替代方法,但如果您无法控制修改定义枚举的源标头,则可能不适用。

完整的答案在这里:如何轻松地将 c++ 枚举映射到字符串

我的宏解决方案

更完整/更高效的代码。但最多可以处理 170 个枚举值,如果需要超过 170 个,请随时修改此代码。此代码生成用于操作枚举的扩展方法。

  • ToString - 返回枚举的名称。如果另一个枚举具有相同的数值,则返回的名称是第一个具有值的枚举。
  • ToIntegralString - 将数值返回到字符串。
  • ToIntegral - 返回枚举的数值。type 是枚举的基础类型。
  • 解析 - 将字符串转换为枚举值,它可能会引发异常。
  • 解析 - 将数值转换为枚举值,数值类型与枚举的 unlying 类型相同,可能会抛出异常。
  • GetValues - 返回包含所有 enun 值的向量。

语法 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)

  • EnumName - 枚举类的名称。
  • UType - 基础类型的名称。
  • VarN - 枚举值的名称。
  • OperN - 赋值运算符可以是 EQ 表示等于,或者 _ 表示无运算符。
  • ValN - 基础类型值。如果 OperN 为,则忽略此值。_

此宏需要 3 个组 - Var、Oper、Val 示例:
它生成 X
|它生成 Y = 2
|它生成 Z
X,_,_Y,EQ,2Z,_,2

例如

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)

它生成

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

使用这个

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, _, _)

` 它生成

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

完整代码

您可以从以下位置测试/运行/任何此代码

它不能粘贴到这里。代码太长。

这里。主程序部分

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

输出

VisualC++

vc++