枚举到现代C ++ 11 / C ++ 14和未来的C ++ 17 / C ++ 20中 [英] enum to string in modern C++11 / C++14 and future C++17 / C++20

查看:748
本文介绍了枚举到现代C ++ 11 / C ++ 14和未来的C ++ 17 / C ++ 20中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  • 2008 c Is there a simple way to convert C++ enum to string?
  • 2008 c Easy way to use variables of enum types as string in C?
  • 2008 c++ How to easily map c++ enums to strings
  • 2008 c++ Making something both a C identifier and a string?
  • 2008 c++ Is there a simple script to convert C++ enum to string?
  • 2009 c++ How to use enums as flags in C++?
  • 2011 c++ How to convert an enum type variable to a string?
  • 2011 c++ Enum to String C++
  • 2011 c++ How to convert an enum type variable to a string?
  • 2012 c How to convert enum names to string in c
  • 2013 c Stringifying an conditionally compiled enum in C
  • Elegant way using C++11, C++14 or C++17 new features
  • Or something ready-to-use in Boost
  • Else something planned for C++20

一个例子往往比一个很长的解释要好。

你可以编译并运行这个代码段 Coliru

另一个前一个例子也可用)

An example is often better than a long explanation.
You can compile and run this snippet on Coliru.
(Another former example is also available)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code 
auto magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}



限制




  • 请勿重复其他答案基本链接

  • 请避免膨胀的基于宏的答案,或尝试尽可能地减少$ $ $ $ $ $ $ $ $ $开始。

  • 请不要手动枚举 - > string 映射

  • Constraints

    • Please no invaluable duplication of other answers or basic link.
    • Please avoid bloat macro-based answer, or try to reduce the #define overhead as minimum as possible.
    • Please no manual enum -> string mapping.

      • 支持枚举从不同于零的数字开始的值

      • 支持否定枚举

      • 支持零碎的枚举

      • 支持类枚举 (C ++ 11)

      • 支持类枚举:< type> 有任何允许的< type> ; (C ++ 11)

      • 编译时(非运行时)检查

      • 运行时(即 std :: map 不是最好的主意)

      • constexpr (C ++ 11,在C ++ 14中放松)

      • noexcept (C ++ 11)

      • snippet C ++ 14 / C ++ 17友好

      • C ++ 最新技术

      • Support enum values starting from a number different from zero
      • Support negative enum values
      • Support fragmented enum values
      • Support class enum (C++11)
      • Support class enum : <type> having any allowed <type> (C++11)
      • Compile-time (not run-time) checking
      • Else fast execution at run-time (i.e. std::map is not the best idea)
      • constexpr (C++11, relaxed in C++14)
      • noexcept (C++11)
      • snippet C++14/C++17 friendly
      • C++ State of the art

      一个可能的想法可能是使用C ++编译器在编译时使用基于可变模板类 constexpr 函数的元编程技巧生成C ++代码...

      One possible idea could be using the C++ compiler capabilities to generate C++ code at compilation-time using meta-programming tricks based on variadic template class and constexpr functions...

      推荐答案

      有一种方法可以在当前的C ++中执行枚举,如下所示:

      There is a way to do enum to string in current C++ that looks like this:

      ENUM(Channel, char, Red = 1, Green, Blue)
      
      // "Same as":
      // enum class Channel : char { Red = 1, Green, Blue };
      

      用法:

      Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
      c._to_string();                                  // string "Green"
      
      for (Channel c : Channel::_values())
          std::cout << c << std::endl;
      
      // And so on...
      

      所有操作都可以 constexpr 。您还可以实现由@ecatmur提供的C ++ 17反思方案。

      All operations can be made constexpr. You can also implement the C++17 reflection proposal mentioned in the answer by @ecatmur.


      • 只有一个宏。我相信这是最小可能的,因为预处理器字符串()是将令牌转换为当前C ++中的字符串的唯一方法。

      • 宏非常不引人注目 - 常量声明(包括初始化器)粘贴到内置的枚举声明中。这意味着它们具有与内置枚举中相同的语法和含义。

      • 重复被删除。

      • 实现是最自然和有用的至少C ++ 11,由于 constexpr 。它也可以使用C ++ 98 + __ VA_ARGS __ 。这是绝对现代化的C ++。

      • There is only one macro. I believe this is the minimum possible, because preprocessor stringization (#) is the only way to convert a token to a string in current C++.
      • The macro is pretty unobtrusive – the constant declarations, including initializers, are pasted into a built-in enum declaration. This means they have the same syntax and meaning as in a built-in enum.
      • Repetition is eliminated.
      • The implementation is most natural and useful in at least C++11, due to constexpr. It can also be made to work with C++98 + __VA_ARGS__. It is definitely modern C++.

      宏的定义有点涉及,在几个方面回答这个问题。

      The macro's definition is somewhat involved, so I'm answering this in several ways.


      • 这个答案的大部分是一个我认为适合StackOverflow空间限制的实现。 / li>
      • 还有一个 CodeProject文章在长格式教程中描述了实现的基础知识。 [我应该在这里移动吗?我认为这是一个SO答案太多了。

      • 有一个完整的-featured库更好的枚举在单个头文件中实现宏。它还实施 N4428类型属性查询,当前修订版本C ++ 17反思提案N4113。所以,至少对于通过这个宏声明的枚举,你现在可以使用C ++ 11 / C ++ 14中的C ++ 17枚举反射。

      • The bulk of this answer is an implementation that I think is suitable for the space constraints on StackOverflow.
      • There is also a CodeProject article describing the basics of the implementation in a long-form tutorial. [Should I move it here? I think it's too much for a SO answer].
      • There is a full-featured library "Better Enums" that implements the macro in a single header file. It also implements N4428 Type Property Queries, the current revision of the C++17 reflection proposal N4113. So, at least for enums declared through this macro, you can have the proposed C++17 enum reflection now, in C++11/C++14.

      将这个答案扩展到图书馆的功能是直接的,没有什么重要在这里。然而,这是非常乏味的,并且存在编译器可移植性问题。

      It is straightforward to extend this answer to the features of the library – nothing "important" is left out here. It is, however, quite tedious, and there are compiler portability concerns.

      免责声明:我是CodeProject文章和图书馆。

      Disclaimer: I am the author of both the CodeProject article and the library.

      您可以尝试此答案中的代码图书馆实现的N4428 在Wandbox上线。图书馆文件还包含一个如何使用它作为N4428的概述,其中解释该提案的枚举部分。

      You can try the code in this answer, the library, and the implementation of N4428 live online in Wandbox. The library documentation also contains an overview of how to use it as N4428, which explains the enums portion of that proposal.

      以下代码实现枚举和字符串之间的转换。然而,它可以扩展到做其他事情,如迭代。这个答案在一个 struct 中包含一个枚举。您还可以生成一个特征 struct ,而不是枚举。

      The code below implements conversions between enums and strings. However, it can be extended to do other things as well, such as iteration. This answer wraps an enum in a struct. You can also generate a traits struct alongside an enum instead.

      策略是生成如下内容: / p>

      The strategy is to generate something like this:

      struct Channel {
          enum _enum : char { __VA_ARGS__ };
          constexpr static const Channel          _values[] = { __VA_ARGS__ };
          constexpr static const char * const     _names[] = { #__VA_ARGS__ };
      
          static const char* _to_string(Channel v) { /* easy */ }
          constexpr static Channel _from_string(const char *s) { /* easy */ }
      };
      

      问题是:


      1. 我们将以$ code> {Red = 1,Green,Blue} 作为值数组的初始化器。这是不正确的C ++,因为 Red 不是一个可分配的表达式。这通过将每个常量转换为具有赋值运算符的类型 T 来解决,但将丢弃赋值: {(T)Red = 1, (T)绿色,(T)蓝色}

      2. 同样,我们将最终得到 {Red = 1绿色,蓝色} 作为名称数组的初始化器。我们需要修剪= 1。我不知道在编译时这样做的好办法,所以我们将把它推迟到运行时间。因此, _to_string 不会是 constexpr ,而是 _from_string 仍然可以是 constexpr ,因为我们可以将空格和等号作为终止符与未拼接的字符串进行比较。

      3. 需要一个映射宏,可以在 __ VA_ARGS __ 中的每个元素应用另一个宏。这是很标准的这个答案包括一个可以处理多达8个元素的简单版本。

      4. 如果宏真的是自包含的,那么需要声明不需要单独定义的静态数据。在实践中,这意味着阵列需要特殊处理。有两种可能的解决方案:命名空间范围的 constexpr (或只是 const )数组,或非 constexpr 静态内联函数。这个答案的代码是针对C ++ 11而采用前一种方法。 CodeProject文章适用于C ++ 98并采用后者。

      1. We will end up with something like {Red = 1, Green, Blue} as the initializer for the values array. This is not valid C++, because Red is not an assignable expression. This is solved by casting each constant to a type T that has an assignment operator, but will drop the assignment: {(T)Red = 1, (T)Green, (T)Blue}.
      2. Similarly, we will end up with {"Red = 1", "Green", "Blue"} as the initializer for the names array. We will need to trim off the " = 1". I am not aware of a great way to do this at compile time, so we will defer this to run time. As a result, _to_string won't be constexpr, but _from_string can still be constexpr, because we can treat whitespace and equals signs as terminators when comparing with untrimmed strings.
      3. Both the above need a "mapping" macro that can apply another macro to each element in __VA_ARGS__. This is pretty standard. This answer includes a simple version that can handle up to 8 elements.
      4. If the macro is to be truly self-contained, it needs to declare no static data that requires a separate definition. In practice, this means arrays need special treatment. There are two possible solutions: constexpr (or just const) arrays at namespace scope, or regular arrays in non-constexpr static inline functions. The code in this answer is for C++11 and takes the former approach. The CodeProject article is for C++98 and takes the latter.






      代码




      Code

      #include <cstddef>      // For size_t.
      #include <cstring>      // For strcspn, strncpy.
      #include <stdexcept>    // For runtime_error.
      
      
      
      // A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
      // macro(a) macro(b) macro(c) ...
      // The helper macro COUNT(a, b, c, ...) expands to the number of
      // arguments, and IDENTITY(x) is needed to control the order of
      // expansion of __VA_ARGS__ on Visual C++ compilers.
      #define MAP(macro, ...) \
          IDENTITY( \
              APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
                  (macro, __VA_ARGS__))
      
      #define CHOOSE_MAP_START(count) MAP ## count
      
      #define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))
      
      #define IDENTITY(x) x
      
      #define MAP1(m, x)      m(x)
      #define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
      #define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
      #define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
      #define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
      #define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
      #define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
      #define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))
      
      #define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
          count
      
      #define COUNT(...) \
          IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))
      
      
      
      // The type "T" mentioned above that drops assignment operations.
      template <typename U>
      struct ignore_assign {
          constexpr explicit ignore_assign(U value) : _value(value) { }
          constexpr operator U() const { return _value; }
      
          constexpr const ignore_assign& operator =(int dummy) const
              { return *this; }
      
          U   _value;
      };
      
      
      
      // Prepends "(ignore_assign<_underlying>)" to each argument.
      #define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
      #define IGNORE_ASSIGN(...) \
          IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))
      
      // Stringizes each argument.
      #define STRINGIZE_SINGLE(e) #e,
      #define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))
      
      
      
      // Some helpers needed for _from_string.
      constexpr const char    terminators[] = " =\t\r\n";
      
      // The size of terminators includes the implicit '\0'.
      constexpr bool is_terminator(char c, size_t index = 0)
      {
          return
              index >= sizeof(terminators) ? false :
              c == terminators[index] ? true :
              is_terminator(c, index + 1);
      }
      
      constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                       size_t index = 0)
      {
          return
              is_terminator(untrimmed[index]) ? s[index] == '\0' :
              s[index] != untrimmed[index] ? false :
              matches_untrimmed(untrimmed, s, index + 1);
      }
      
      
      
      // The macro proper.
      //
      // There are several "simplifications" in this implementation, for the
      // sake of brevity. First, we have only one viable option for declaring
      // constexpr arrays: at namespace scope. This probably should be done
      // two namespaces deep: one namespace that is likely to be unique for
      // our little enum "library", then inside it a namespace whose name is
      // based on the name of the enum to avoid collisions with other enums.
      // I am using only one level of nesting.
      //
      // Declaring constexpr arrays inside the struct is not viable because
      // they will need out-of-line definitions, which will result in
      // duplicate symbols when linking. This can be solved with weak
      // symbols, but that is compiler- and system-specific. It is not
      // possible to declare constexpr arrays as static variables in
      // constexpr functions due to the restrictions on such functions.
      //
      // Note that this prevents the use of this macro anywhere except at
      // namespace scope. Ironically, the C++98 version of this, which can
      // declare static arrays inside static member functions, is actually
      // more flexible in this regard. It is shown in the CodeProject
      // article.
      //
      // Second, for compilation performance reasons, it is best to separate
      // the macro into a "parametric" portion, and the portion that depends
      // on knowing __VA_ARGS__, and factor the former out into a template.
      //
      // Third, this code uses a default parameter in _from_string that may
      // be better not exposed in the public interface.
      
      #define ENUM(EnumName, Underlying, ...)                               \
      namespace data_ ## EnumName {                                         \
          using _underlying = Underlying;                                   \
          enum { __VA_ARGS__ };                                             \
                                                                            \
          constexpr const size_t           _size =                          \
              IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                            \
          constexpr const _underlying      _values[] =                      \
              { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                            \
          constexpr const char * const     _raw_names[] =                   \
              { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
      }                                                                     \
                                                                            \
      struct EnumName {                                                     \
          using _underlying = Underlying;                                   \
          enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                            \
          const char * _to_string() const                                   \
          {                                                                 \
              for (size_t index = 0; index < data_ ## EnumName::_size;      \
                   ++index) {                                               \
                                                                            \
                  if (data_ ## EnumName::_values[index] == _value)          \
                      return _trimmed_names()[index];                       \
              }                                                             \
                                                                            \
              throw std::runtime_error("invalid value");                    \
          }                                                                 \
                                                                            \
          constexpr static EnumName _from_string(const char *s,             \
                                                 size_t index = 0)          \
          {                                                                 \
              return                                                        \
                  index >= data_ ## EnumName::_size ?                       \
                          throw std::runtime_error("invalid identifier") :  \
                  matches_untrimmed(                                        \
                      data_ ## EnumName::_raw_names[index], s) ?            \
                          (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                                  index] :  \
                  _from_string(s, index + 1);                               \
          }                                                                 \
                                                                            \
          EnumName() = delete;                                              \
          constexpr EnumName(_enum value) : _value(value) { }               \
          constexpr operator _enum() const { return (_enum)_value; }        \
                                                                            \
        private:                                                            \
          _underlying     _value;                                           \
                                                                            \
          static const char * const * _trimmed_names()                      \
          {                                                                 \
              static char     *the_names[data_ ## EnumName::_size];         \
              static bool     initialized = false;                          \
                                                                            \
              if (!initialized) {                                           \
                  for (size_t index = 0; index < data_ ## EnumName::_size;  \
                       ++index) {                                           \
                                                                            \
                      size_t  length =                                      \
                          std::strcspn(data_ ## EnumName::_raw_names[index],\
                                       terminators);                        \
                                                                            \
                      the_names[index] = new char[length + 1];              \
                                                                            \
                      std::strncpy(the_names[index],                        \
                                   data_ ## EnumName::_raw_names[index],    \
                                   length);                                 \
                      the_names[index][length] = '\0';                      \
                  }                                                         \
                                                                            \
                  initialized = true;                                       \
              }                                                             \
                                                                            \
              return the_names;                                             \
          }                                                                 \
      };
      

      // The code above was a "header file". This is a program that uses it.
      #include <iostream>
      #include "the_file_above.h"
      
      ENUM(Channel, char, Red = 1, Green, Blue)
      
      constexpr Channel   channel = Channel::_from_string("Red");
      
      int main()
      {
          std::cout << channel._to_string() << std::endl;
      
          switch (channel) {
              case Channel::Red:   return 0;
              case Channel::Green: return 1;
              case Channel::Blue:  return 2;
          }
      }
      
      static_assert(sizeof(Channel) == sizeof(char), "");
      

      以上程序打印 Red 会期待有一种类型的安全性,因为您不能在不初始化的情况下创建枚举,并从开关中删除​​其中一个案例将导致编译器发出警告(取决于你的编译器和标志)。另外,请注意,在编译期间,Red已转换为枚举。

      The program above prints Red, as you would expect. There is a degree of type safety, since you can't create an enum without initializing it, and deleting one of the cases from the switch will result in a warning from the compiler (depending on your compiler and flags). Also, note that "Red" was converted to an enum during compilation.

      这篇关于枚举到现代C ++ 11 / C ++ 14和未来的C ++ 17 / C ++ 20中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆