【问题标题】:Creating a string list and an enum list from a C++ macro从 C++ 宏创建字符串列表和枚举列表
【发布时间】:2011-07-28 15:57:53
【问题描述】:

为了使我的代码更短更容易更改,我想替换类似的东西

enum{ E_AAA, E_BBB, E_CCC };
static const char *strings{"AAA", "BBB", "CCC" };

使用宏,例如 INIT(AAA, BBB, CCC);但是当我尝试使用可变参数和字符串化做一个宏时,我得到一个错误,因为没有声明参数。

你知道怎么做吗?

【问题讨论】:

  • 你的宏到底长什么样?
  • 看看 Boost.Preprocessor,它很丑(由于 cpp 的限制),但它提供了编写对序列进行操作的宏的方法。
  • 听起来你在输出strings 定义时忘记了引号。另外,请您选择一种语言:C 还是 C++?
  • 这是一个非常有用的问题并帮助了我。感谢您的提问。
  • 基本c++枚举类实现在这里stackoverflow.com/questions/207976/…

标签: c++ c macros c-preprocessor variadic


【解决方案1】:

这是我几天前学到的解决方案。处理您的问题的简化版是:

#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\
    enum name { v1, v2, v3, v4, v5, v6, v7};\
    const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};

ENUM_MACRO(Week, Sun, Mon, Tue, Wed, Thu, Fri, Sat);

但您可以使用函数调用的改进版本,如下所示:

#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\
    enum name { v1, v2, v3, v4, v5, v6, v7};\
    const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};\
    const char *name##ToString(value) { return name##Strings[value]; }

ENUM_MACRO(Week, Sun, Mon, Tue, Wed, Thu, Fri, Sat);

这将变成:

  enum Week { Sun, Mon, Tue, Wed, Thu, Fri, Sat}; 
  const char *WeekStrings[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 
  const char *WeekToString(value) { return WeekStrings[value]; };

您甚至可以对第一个元素使用 偏移量,如下所示:

#define ENUM_MACRO(name, offset, v1, v2, v3, v4, v5, v6, v7)\
    enum name { v1 =  offset, v2, v3, v4, v5, v6, v7};\
    const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};\
    const char *name##ToString(value) { return name##Strings[value - offset ]; }

ENUM_MACRO(Week, 1, Sun, Mon, Tue, Wed, Thu, Fri, Sat);

我希望这会有所帮助。

保重, 贝科

参考:

Print the month question,Kush,Danny Varod 回答

【讨论】:

  • 请版主删除 CW 标志。谢谢。
  • 我们可以看到 OP 在站点中没有激活。主持人是否有可能(并且可取!)接受我的答案作为“已接受的答案”(如果有人认为它对此有价值,当然)?感谢您对该程序的任何评论。
  • 除了问题作者之外,任何人都不可能接受答案。像这样的情况有discussions about an automatic default,但“接受”从来没有打算表示“最好”,只是问题作者可能最相关的答案。只有问题作者肯定知道这一点,所以我们一直非常不愿意引入这种变化。
  • 很好,但是宏不能像其他答案之一中的 CREATE_ENUM 和 CREATE_STRINGS 宏那样重复使用。
  • 谢谢保罗(乐队的好名字)。这是在 2011/2014 年发表的评论,因此背景不同。无论如何,我仍然喜欢我的回答。 “一般事物”的宏从未被充分利用。您花费大量时间创建一个通用宏,只是为了在编写另一个软件时重新创建另一个“更好”。简单更好。保重。
【解决方案2】:

你可以用一点宏魔法来做到这一点:

#define FRUITS \
    etype(Unknown), \
    etype(Apple),   \
    etype(Orange),  \
    etype(Banana),  \
    etype(Apricot), \
    etype(Mango)

#define etype(x) F_##x

typedef enum { FRUITS } Fruit;

#undef etype
#define etype(x) #x

static const char *strFruit[] = { FRUITS };

这是一个测试程序:

#include <iostream>
#include <exception>
#include <vector>

#define FRUITS \
    etype(Unknown), \
    etype(Apple),   \
    etype(Orange),  \
    etype(Banana),  \
    etype(Apricot), \
    etype(Mango)

#define etype(x) F_##x

typedef enum { FRUITS } Fruit;

#undef etype
#define etype(x) #x

static const char *strFruit[] = { FRUITS };

const char *enum2str (Fruit f)
{
    return strFruit[static_cast<int>(f)];
}

Fruit str2enum (const char *f)
{
    const int n = sizeof(strFruit) / sizeof(strFruit[0]);
    for (int i = 0; i < n; ++i)
    {
        if (strcmp(strFruit[i], f) == 0)
            return (Fruit) i;
    }
    return F_Unknown;
}

int main (int argc, char *argv[])
{
    std::cout << "I like " << enum2str(F_Mango) << std::endl;
    std::cout << "I do not like " << enum2str(F_Banana) << std::endl;
    std::vector<char *> v;
    v.push_back("Apple");
    v.push_back("Mango");
    v.push_back("Tomato");
    for (int i = 0; i < v.size(); ++i)
    {
        const Fruit f = str2enum(v[i]);
        if (f == F_Unknown)
            std::cout << "Is " << v[i] << " a fruit?" << std::endl;
        else
            std::cout << v[i] << " is a fruit" << std::endl;
    }
    return 0;
}

它输出:

I like Mango
I do not like Banana
Apple is a fruit
Mango is a fruit
Is Tomato a fruit?

【讨论】:

【解决方案3】:

这是我的解决方案:

#define FRUITS(fruit) \
  fruit(Apple)        \
  fruit(Orange)       \
  fruit(Banana)       

#define CREATE_ENUM(name) \
  F_##name,

#define CREATE_STRINGS(name) \
  #name,

诀窍在于“fruit”是宏“FRUITS”的参数 并将被您传递的内容所取代。 例如:

FRUITS(CREATE_ENUM)

将扩展为:

F_Apple, F_Orange, F_Banana, 

让我们创建枚举和字符串数组:

enum fruit {
  FRUITS(CREATE_ENUM)
};

const char* fruit_names[] = {
  FRUITS(CREATE_STRINGS)
};

【讨论】:

  • 这太可爱了!我真的需要这个,但从没想过有人会解决它。
【解决方案4】:

这是一个使用 Boost.Preprocessor 的解决方案:

#include <boost/preprocessor.hpp>

#define DEFINE_ENUM_DECL_VAL(r, name, val) BOOST_PP_CAT(name, BOOST_PP_CAT(_, val))
#define DEFINE_ENUM_VAL_STR(r, name, val) BOOST_PP_STRINGIZE(val)
#define DEFINE_ENUM(name, val_seq)                                                 \
  enum name {                                                                      \
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(DEFINE_ENUM_DECL_VAL, name, val_seq)) \
  };                                                                               \
  static const char* BOOST_PP_CAT(name, _strings[] = ) {                           \
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(DEFINE_ENUM_VAL_STR, name, val_seq)) \
  };

DEFINE_ENUM(E, (AAA)(BBB)(CCC))

(AAA)(BBB)(CCC)是一个Boost.Preprocessor序列的树元素AAA、BBB和CCC;宏将枚举名称附加到它的模态:

enum E { E_AAA, E_BBB, E_CCC };
static const char* E_strings[] = { "AAA", "BBB", "CCC" };

【讨论】:

    【解决方案5】:

    一种方法是使用X-Macros,它基本上是一种定义宏的方法,然后用于生成比简单宏容易允许的更复杂的结构。 Here is an example 完全按照你的要求去做。

    【讨论】:

    • +1 我只是在写这个,但是你找到了一个已经存在的例子:)
    • X-Macros 的坏例子,因为处理器已经可以字符串化参数;这意味着整个 X(red, "red") 可以用 X(red) 替换为更好的 X。
    【解决方案6】:

    我参加聚会有点晚了,但这里有另一个建议。
    它创建了一个强类型枚举类,比如MyEnumName 和一个伴随的静态帮助类Enumator&lt;MyEnumName&gt;
    它比以前的答案更大,因为它具有更多功能,例如用于从/到字符串转换的流运算符。
    请注意,由于使用了索引序列,它依赖于 c++ 14 标准。

    用法:

    /* One line definition - no redundant info */
    ENUM_DEFINE(WeekDay /*first item is enum name*/, 
        Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday);
    
    /* works seemlessly with streams (good for logging) */
    auto dayOne = WeekDay::Sunday;
    std::cout << "day of week is: " << day_of_week;
    
    /* explicit construction from string using WeekDay_enum companion class*/
    auto dayTwo = Enumator<WeekDay>::fromString("Tuesday");
    
    
    /*Iterate over all enum values using Enumator<WeekDay> companion class*/
    std::cout << "Days of the week are:\n"
    for (auto enumVal : Enumator<WeekDay>::getValues()) {
        std::cout << enumVal << "\n";
    }
    

    来源:

        #include <array>
        #include <string>
        #include <sstream>
        #include <stdexcept>
    
    template<typename E>
    using isEnum = typename std::enable_if<std::is_enum<E>::value>::type;
    
    template<typename E, typename = isEnum<E>>
    constexpr static int enumSize() {
        return 0;
    }
    
    template<typename E, typename = isEnum<E>>
    inline static std::string getEnumStringValues() {
        return "";
    }
    
    
    /*Enum companion class to hold the methods that can't be declared in an enum*/
    template<typename EnumType, isEnum<EnumType>* = nullptr>
    class Enumator
    {
        Enumator() = delete; /* prevents instantiation */
    
    public:
    
        constexpr static int size() {
            return enumSize<EnumType>();
        }
        /* list of all enum values a string */
        static auto const& getValuesStr()
        {
            static std::array<std::string, size()> values;
            if (values[0].empty()) {
                std::string valuesStr = getEnumStringValues<EnumType>();
                std::stringstream ss(valuesStr);
                for (auto& value : values) {
                    std::getline(ss, value, ',');                   
                }
            }
            return values;
        };
    
        /* list of all enum values */
        static auto const& getValues()
        {
            static std::array<EnumType, size()> values{ make_array(std::make_index_sequence<size()>()) };
            return values;
        };
    
        /* To/from string conversion */
        constexpr static std::string const& toString(EnumType arg) {
            return getValuesStr()[static_cast<unsigned>(arg)];
        }
    
        static EnumType fromString(std::string const& val)
        {
            /* Attempt at converting from string value */
            auto const& strValues = getValuesStr();
    
            for (unsigned int i = 0; i < strValues.size(); i++)
            { 
                if (val == strValues[i])
                {
                    return static_cast<EnumType>(i);
                }
            }
            throw std::runtime_error("No matching enum value found for token: " + val);
        }
    
    private:
        /* Helper method to initialize array of enum values */
        template<std::size_t...Idx>
        static auto make_array(std::index_sequence<Idx...>)
        {
            return std::array<EnumType, size()>{{static_cast<EnumType>(Idx)...}};
        }
    };
    
    template<typename EnumType, isEnum<EnumType>* = nullptr>
    inline std::istream& operator>> (std::istream& input, EnumType& arg)
    {
        std::string val;
        input >> val;
        arg = Enumator<EnumType>::fromString(val);
        return input;
    }
    
    template<typename EnumType, isEnum<EnumType>* = nullptr>
    inline std::ostream& operator<< (std::ostream& output, const EnumType& arg)
    {
        return output << Enumator<EnumType>::toString(arg);
    }
    
    #define ENUM_DEFINE(EnumName,...)\
        \
        enum class EnumName;\
        \
        template<>\
        constexpr int enumSize<EnumName>() {\
            /*Trick to get the number of enum members:*/\
            /*dump all the enum values in an array and compute its size */\
            enum EnumName { __VA_ARGS__ }; \
            EnumName enumArray[]{ __VA_ARGS__ }; \
            return sizeof(enumArray) / sizeof(enumArray[0]); \
        }\
        \
        template<>\
        inline std::string getEnumStringValues<EnumName>() { return #__VA_ARGS__; }\
        \
        enum class EnumName : int { __VA_ARGS__ }
    

    【讨论】:

    • 这个代码示例很有帮助,是一个好的开始。但请注意,它有一些可能需要解决的缺点。首先,在当前形式下,宏不能在命名空间或类中使用。其次,它没有明确地从枚举字符串中删除空格。这不是严格要求的,但可能出乎意料。这两点都可以通过一些努力来解决,从而提供一个很好且非常灵活的解决方案。谢谢@gg99!
    【解决方案7】:

    处理这个问题的一种方法是定义一个列表宏,即扩展为另一个宏的东西,留给用户定义。例如:

    #define MY_LIST MY_ENTRY(AAA) MY_ENTRY(BBB) MY_ENTRY(CCC)
    

    定义enum

    #define MY_ENTRY(x) E_##x,
    enum name
    {
      MY_LIST
      NUMBER_OF_ELEMENTS    /* Needed to eat trailing comma (not needed in C99, but in C++) */
    };
    #undef MY_ENTRY
    

    定义字符串:

    #define MY_ENTRY(x) #x,
    static const char *strings[] = { MY_LIST };
    #undef MY_ENTRY
    

    就我个人而言,我发现这比 X 宏更容易使用,因为它不依赖于包含文件的魔法。

    【讨论】:

    • 尾随逗号在枚举中完全没问题。
    • 我检查了标准,在 C99 中它们没问题(这对我来说是个新闻——谢谢)。但是,C89 和 C++ (1998) 都不接受它们。
    • 你说得对,Comeau Online 在 C++03 严格模式下也拒绝尾随逗号。 :) 那就忽略我之前的评论吧。
    • 看起来您刚刚发现 Comeau 编译器存在问题,根据草案,C++03 应该接受尾随逗号。
    • @Lindydancer:根据已发布的标准,C++03 不应接受尾随逗号。
    【解决方案8】:

    对于一个简单的解决方案,我推荐X-Macros

    对于添加了几个其他功能(如范围检查、增强的类型安全性、可选的关联数据等)的更复杂的解决方案,有一个建议(但从未最终确定)Boost.Enum library

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多