2011-04-03 178 views
17

字符串列表和枚舉列表爲了使我的代碼更短,更容易改變我要替換類似創建從C++宏

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

與宏,像INIT(AAA,BBB, CCC);但是當我嘗試使用可變參數進行宏操作時,並且由於參數未聲明,導致出現錯誤。

任何想法如何做到這一點?

+3

爲什麼說你的宏樣子? – Xeo 2011-04-03 14:44:48

+0

看看Boost.Preprocessor,它很醜陋(由於cpp的限制),但它提供了編寫操作序列的宏的方法。 – Begemoth 2011-04-03 14:46:37

+2

聽起來就像你在輸出'strings'定義時忘記了引號。另外,你可以選擇一種語言:C或C++? – 2011-04-03 14:46:57

回答

4

這樣做的一種方法是使用X-Macros,這基本上是一種定義宏的方法,然後用它來生成比簡單宏容易允許的更復雜結構。 Here is an example做你正在問什麼。

+0

+1我只是寫這個,但你發現一個已經存在的例子:) – digEmAll 2011-04-03 14:52:29

+0

X宏的壞榜樣,因爲procressor已經可以將參數串聯起來;這意味着整個X(紅色,「紅色」)可以被X(紅色)替換爲更好的X. – UKMonkey 2017-09-13 11:27:15

2

這裏是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)是樹中的元素AAA,BBB和CCC的Boost.Preprocessor序列。

2

處理此問題的一種方法是定義一個列表宏,即擴展到剩下供用戶定義的另一個宏的東西。例如:

#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宏來工作,因爲這不依賴於包含文件的魔法。

+0

尾隨逗號在枚舉中是完全正確的。 – Xeo 2011-04-03 16:11:49

+0

我有檢查標準,在C99他們沒事(這是我的消息 - 謝謝)。然而,C89和C++(1998)都不接受它們。 – Lindydancer 2011-04-03 16:26:59

+0

你是對的,Comeau Online也拒絕C++ 03嚴格模式中的尾隨逗號。 :)然後就忽略我以前的評論。 – Xeo 2011-04-03 16:32:58

22

這裏有一個我前幾天學到的解決方案。該簡化版本,參加你的問題是:

#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,由庫什,答案由丹尼Varod

+0

請主持人刪除CW標誌。謝謝。 – 2011-04-03 17:30:07

+0

正如我們所看到的,OP在站點中不活躍。對主持人是否可能(也是可取的)接受我的答案作爲「接受的答案」(當然,如果有人認爲它有價值)?感謝您對程序的任何評論。 – 2014-09-26 02:07:49

+0

除問題作者以外,任何人都不可能接受答案。對於這種情況,有[關於自動默認的討論](http://meta.stackexchange.com/questions/8692/force-accepted-answers-on-questions-by-inactive-users),但「接受」是從來沒有打算表明'最好',只是問題作者可能涉及的答案最多。只有問題作者知道,當然,所以我們一直很不情願引入這種變化。 – 2014-09-26 03:34:25

1

對於一個簡單的解決辦法,我建議像X-Macros

對於一個更復雜的解決方案,增加了一些其他功能(如範圍檢查,增強型安全性,可選的相關數據等),有一個建議(但從未最終確定)Boost.Enum library

11

你可以用一點宏觀魔術做到這一點:

#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? 
+1

我喜歡這個,但是我在這裏發現了一個類似但更乾淨的方式:http://stackoverflow.com/a/238157/599142 – tr3w 2012-07-13 16:14:20

7

這裏是我的解決方案:

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

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

#define CREATE_STRINGS(name) \ 
    #name, 

訣竅是,「水果」是宏'水果' 的論點,並將被你傳遞給你的東西所取代。 例如:

FRUITS(CREATE_ENUM) 

將擴展到這一點:

F_Apple, F_Orange, F_Banana, 

讓我們創建的枚舉和字符串數組:

enum fruit { 
    FRUITS(CREATE_ENUM) 
}; 

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

這很可愛!我真的需要這個,但從來沒有想過有人會解決它。 – 2016-06-23 07:42:04

0

我有點遲到了,但這裏是另一個建議。
它創建一個強類型枚舉類,例如MyEnumName和一個伴隨靜態幫助類Enumator<MyEnumName>
它比以前的答案更大,因爲它有更多的功能,例如流操作符用於從/到字符串的轉換。
請注意,由於使用索引序列,它依賴於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__ }