在C中将枚举类型的变量用作字符串的简单方法?


87

这是我想要做的:

typedef enum { ONE, TWO, THREE } Numbers;

我正在尝试编写一个函数,该函数将执行类似于以下的切换情况:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

除了在每种情况下进行定义之外,还有没有办法像我在上面尝试的那样使用enum变量进行设置?

Answers:


14

没有内置的解决方案。最简单的方法是使用一个数组,char*其中枚举的int值索引到包含该枚举的描述性名称的字符串。如果您的稀疏enum(一个不是从0开始或编号上有间隔),其中某些int映射足够高,使得基于数组的映射不切实际,那么您可以改用哈希表。


对此进行扩展,如果确实是线性递增列表,则可以使用编辑器的宏工具记录每个名称并将其解析为字符串。几乎不需要额外的输入,并且您一开始就不需要定义。我单击复制的最后一个宏上的记录,在其后添加引号,然后继续进行下一行的相同位置。我推停。我按运行X次,但是有很多次(或只是一步)。然后,我可以将其包装在字符串数组中。
2262111

70

从技术制作的东西都一个C和一个字符串?可以在这里使用。

像使用此类预处理器一样,编写和理解预处理器部分可能很困难,包括将宏传递给其他宏,并涉及使用#和##运算符,但使用起来确实很容易。我发现这种样式对于长枚举很有用,因为两次枚举相同的列表确实很麻烦。

工厂代码-仅键入一次,通常隐藏在标题中:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

工厂使用

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

可以轻松扩展该技术,以便XX宏接受更多参数,并且您还可以准备更多宏以替代XX以满足不同的需求,类似于本示例中提供的三个。

使用#include / #define / #undef与X-Macros的比较

尽管这与其他人提到的X-Macros类似,但我认为此解决方案更为优雅,因为它不需要#undefing任何内容,这使您可以在工厂中隐藏更多复杂的东西头文件-头文件当您需要定义一个新的枚举时,您根本不需要接触任何东西,因此,新的枚举定义要短得多而且更简洁。


2
我不确定您如何说这比x-macros更好/更糟糕-这 x-macros。的SOME_ENUM(XX)是完全的X宏(准确地,即经过“用户表” XX功能而不是使用#def #undef),然后在转然后整个X-MACRO被传递到DEFINE_ENUM它使用它。不要从解决方案中脱颖而出-它运作良好。只是为了澄清这是使用X宏。
BeeOnRope

1
@BeeOnRope您注意到的区别是很明显的,并且使该解决方案与惯用的 X宏(例如Wikipedia的示例)区分开来。XXover reing 的优势#define在于,前一种模式可用于宏扩展。请注意,只有这样一个简洁的解决方案,所有的解决方案都需要创建并多次包含一个单独的文件来定义新的枚举。
pmttavara

1
另一个技巧是使用枚举名称作为宏名称。您可以简单地编写#define DEFINE_ENUM(EnumType) ...,替换ENUM_DEF(...)EnumType(...)并让用户说#define SomeEnum(XX) ...。C预处理程序将在上下文中扩展SomeEnum为宏调用,后跟括号,否则扩展为常规标记。(当然,如果用户喜欢使用SomeEnum(2)(SomeEnum)2static_cast<SomeEnum>(2)
强制

1
@pmttavara-当然,如果可以进行快速搜索,则x-macros的最常用用法是使用固定的内部宏名称以及#define#undef。难道你不同意,虽然该“用户表”(建议例如,在底部这篇文章)是一种X-宏?当然,我当然也一直将其称为x宏,在我最近使用的C代码库中,它是最常见的形式(这显然是有偏见的)。我可能一直在解析OP错误。
BeeOnRope

2
@BeeOnRope当前的措辞是编辑的结果,正如您当时说服我的那样,它是x宏,即使当时它可能是较少使用的形式(或至少在文章中未提及)。
Suma

62
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

3
这就是cpp的目的。+1。
德里克·特克

5
这是一个很好的答案,这似乎是不使用特殊工具就能做的最好的事情,而我之前已经做过这种事情。但它仍然从未真正感觉到“正确”,我也从未真正喜欢这样做……
Michael Burr 2010年

微小的变化:#define ENUM_END(typ) }; extern const char * typ ## _name_table[];defs.h文件中-这将在使用它的文件中声明您的名称表。(不过,无法找到一种声明表大小的好方法。)另外,我个人不考虑最后一个分号,但是无论哪种方法,其优点都是值得商de的。
克里斯·卢茨

1
@Bill,为什么要typ排队#define ENUM_END(typ) };呢?
和平者

这在我希望将宏定义为“ ONE = 5”的地方
不起作用

13

绝对有一种方法可以做到-使用X()宏。这些宏使用C预处理器从源数据列表构造枚举,数组和代码块。您只需要将新项目添加到包含X()宏的#define中。switch语句将自动扩展。

您的示例可以编写如下:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

有更有效的方法(即,使用X宏创建字符串数组和枚举索引),但这是最简单的演示。


8

我知道您有几个不错的可靠答案,但是您知道C预处理器中的#运算符吗?

它可以让您做到这一点:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

char const *kConstStr[]
安妮·范·罗苏姆

6

尽管我经常需要C或C ++,但它不提供此功能。

尽管最适合于非稀疏枚举,但以下代码可以工作。

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

非稀疏,我的意思不是形式

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

因为那有很大的差距。

这种方法的优点是,它使枚举和字符串的定义彼此靠近。在函数中使用switch语句会使它们变长。这意味着您不太可能更改一个而没有另一个。


6

吻。您将使用枚举执行各种其他切换/案例操作,所以为什么打印应该不同?当您考虑到还有大约100个其他地方可以忘记一个案例时,在打印例程中忘记一个案例并不重要。只需编译-Wall,它将警告不详尽的案例匹配。不要使用“默认”,因为这将使开关穷尽,并且您不会收到警告。相反,让交换机退出并像这样处理默认情况...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}


4

boost :: preprocessor的使用使如下所示的优雅解决方案成为可能:

步骤1:包含头文件:

#include "EnumUtilities.h"

步骤2:使用以下语法声明枚举对象:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

步骤3:使用您的数据:

获取元素数量:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

获取关联的字符串:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

从关联的字符串获取枚举值:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

这看起来干净紧凑,没有要包含的其他文件。我在EnumUtilities.h中编写的代码如下:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

有一些限制,即boost :: preprocessor。在这种情况下,常量列表不能大于64个元素。

按照相同的逻辑,您还可以考虑创建稀疏枚举:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

在这种情况下,语法为:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

用法与上面类似(减去eName ## 2Enum函数,您可以尝试从以前的语法推断)。

我在Mac和Linux上进行了测试,但是请注意boost :: preprocessor可能无法完全移植。


3

通过合并这里的一些技术,我想到了最简单的形式:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

2

如果您使用的是gcc,则可以使用:

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

然后例如打电话

enum_to_string_map[enum1]

1

Mu Dynamics研究实验室(博客存档)中查看想法。我在今年早些时候发现了这一点-我忘记了遇到它的确切上下文-并将其改编为此代码。我们可以争论在前面加上E的优点。它适用于已解决的特定问题,但不适用于一般解决方案。我将其保存在我的“ vignettes”文件夹中-在其中保存了一些有趣的代码片段,以备日后使用。我不好意思说我当时没有记下这个想法的来源。

标题:paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

来源示例:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

不一定是世界上最干净的C预处理器使用-但这确实可以防止多次写出材料。



0

如果枚举索引基于0,则可以将名称放入char *数组中,并使用枚举值对其进行索引。



0

我创建了一个简单的模板化类streamable_enum,该类使用流运算符<<>>并且基于std::map<Enum, std::string>

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

用法:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}

0

这是使用具有以下功能的宏的解决方案:

  1. 只将枚举的每个值写入一次,因此没有要维护的重复列表

  2. 不要将枚举值保存在以后包含#include的单独文件中,因此我可以在任何需要的地方编写它

  3. 不要替换枚举本身,我仍然想定义枚举类型,但是除了它之外,我还希望能够将每个枚举名称映射到相应的字符串(不影响遗留代码)

  4. 对于那些巨大的枚举,搜索应该是快速的,因此最好不要切换

https://stackoverflow.com/a/20134475/1812866


0

我认为像Boost.Fusion one这样的用于适应结构和类的解决方案会很好,他们甚至在某个时候都可以使用枚举作为融合序列。

因此,我仅制作了一些小宏来生成用于打印枚举的代码。这不是完美的,并且与Boost.Fusion生成的样板代码无关,但是可以像Boost Fusion宏一样使用。我确实想生成Boost.Fusion所需的类型,以集成到此基础结构中,该基础结构允许打印结构成员的名称,但这会在以后发生,目前这只是宏:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

下面的旧答案非常糟糕,请不要使用它。:)

旧答案:

我一直在寻找一种解决此问题的方法,而无需过多更改枚举声明语法。我来到了一个使用预处理器从字符串枚举声明中检索字符串的解决方案。

我可以这样定义非稀疏枚举:

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

我可以通过不同的方式与他们互动:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

基于以下定义:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

当我需要对稀疏枚举的支持时,以及当我有更多的时间时,我将使用boost :: xpressive 改进to_stringfrom_string的实现,但这将花费编译时间,因为执行了重要的模板并且生成的可执行文件是可能会更大。但是,这样做的好处是,它比这种难看的手动字符串处理代码更具可读性和可维护性。:D

否则,我总是使用boost :: bimap在枚举值和字符串之间执行这种映射,但是必须手动维护。


0

因为出于所有通常的原因,我都不愿使用宏,所以我使用了一个更有限的宏解决方案,该解决方案的优点是使enum声明宏保持自由。缺点包括必须为每个枚举复制粘贴宏定义,以及在向枚举添加值时必须显式添加宏调用。

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.