【问题标题】:c++: ensure enum values are unique at compile timec++:确保枚举值在编译时是唯一的
【发布时间】:2014-01-27 10:53:14
【问题描述】:

我有以下描述错误代码的enum

 typedef enum {
    et_general           = 0,
    et_INVALID_CLI_FLAG  = 1,
    ...
    et_undef = 500
  } EErrorType;

我显式编写枚举值的主要原因是为了简化调试过程。
无论如何,我想知道是否有办法让编译器抱怨非唯一值。 我总是可以在运行时轻松检查它,但我想避免这种情况。

我已阅读此post 并查看此answer。据我了解,该答案建议以“更难出错”的方式生成枚举。
我想保留枚举定义原样或接近它。

【问题讨论】:

  • N^2 static_asserts 怎么样?
  • 如果您想简化调试,您可能需要将 to_string 函数添加到您的 enum,而不是手动指定值。
  • 创建解析枚举并检查重复值的构建步骤脚本?
  • 对不起,你不能在编译时使用枚举来做到这一点。
  • 使用模板函数检查。可变参数

标签: c++ enums unique


【解决方案1】:

我不确定 Boost 在您的场景中是否可用,因此这里有一个解决方案,其中必须在预处理器序列中定义 enum。然后使用该序列构建枚举和相应的mpl::vector,我们计算vector 的元素是否以奇特的方式唯一。我们可能想先定义一个合适的is_unique 算法,但这应该可以。

#include <boost/mpl/vector.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/sort.hpp>
#include <boost/mpl/unique.hpp>
#include <boost/mpl/size.hpp>

#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/seq/transform.hpp>


#define MYENUM ((FOO, 0))((BAR, 1))((BAZ, 2))

#define GET_NAME(_, __, elem) BOOST_PP_TUPLE_ELEM(2, 0, elem) = BOOST_PP_TUPLE_ELEM(2, 1, elem)
#define GET_VALUE(_, __, elem) boost::mpl::int_<BOOST_PP_TUPLE_ELEM(2, 1, elem)>

enum E {
  BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(GET_NAME, _, MYENUM))
};

typedef boost::mpl::sort< 
  boost::mpl::vector<
    BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(GET_VALUE, _, MYENUM))
    > 
  >::type evalues;

typedef boost::mpl::unique< evalues, boost::is_same<boost::mpl::_1, boost::mpl::_2> >::type uniqued;
static_assert(boost::mpl::size<uniqued>::value == boost::mpl::size<evalues>::value, "enum values not unique");

int main()
{

  return 0;
}

如果将枚举定义更改为:

#define MYENUM ((FOO, 0))((BAR, 1))((BAZ, 2))((BAZZ, 2))

您将收到一条错误消息,说明static_assert failed "enum values not unique"

【讨论】:

    【解决方案2】:

    可以用枚举值作为标签编写一个虚拟的switch 语句—— 将保证它们的唯一性。放置在一个虚拟的未引用函数中,它不会进入可执行文件。

    【讨论】:

    • 这同时是一个令人作呕的组合和一个优雅的 hack。此外,对于类枚举,如果您忘记向虚拟对象添加新的枚举值(不要使用default 标签),体面的编译器会警告您。
    【解决方案3】:

    这个解决方案确实修改了枚举定义,所以如果你能忍受的话......

    这也假设这些值主要是顺序的。

    不要指定值。 在枚举定义之后,让 static_assert 检查人类知道值。

    enum EErrorType : uint16_t
    {
      et_general = 0,
      et_INVALID_CLI_FLAG,
      et_FOO,
      et_BAR,
      ...
      et_undef = 500
    };
    static_assert(EErrorType::et_general          == 0, "Wrong enum value");
    static_assert(EErrorType::et_INVALID_CLI_FLAG == 1, "Wrong enum value");
    static_assert(EErrorType::et_FOO              == 2, "Wrong enum value");
    static_assert(EErrorType::et_BAR              == 3, "Wrong enum value");
    ...
    static_assert(EErrorType::et_undef            == 500, "Wrong enum value");
    

    因此,它们大多是自动分配的,因此是唯一的,您还可以将人类可读的值用于调试和其他目的。

    【讨论】:

      【解决方案4】:

      确保值都是唯一的最好方法就是不明确定义它们。对于您的调试,只需编写一个为您提供值的帮助程序,例如:

      awk 'NR>1{print $1, i; i +=1} eerror.h
      

      这假设eerror.h 的第一行是:

      typedef enum {
      

      【讨论】:

      • 我明白,但正如我所写,我想保留它以供调试建议。
      • @idanshmu 好的,添加到回答中
      【解决方案5】:

      C++ 没有提供任何特性来限制这种方式的枚举。

      如果您准备使用宏来创建您的enum(见下文),您可以在运行时使用一些技巧来完成它,并且您可以使用 gccxml、doxygen 或其他一些东西解析器在构建过程中的某个时间点验证枚举列表。

      更简单但非常微弱,如果您采用每行枚举的编码标准,您可以将__LINE__ 数字的差异与枚举联系起来,但这不会捕捉到例如一个重复,后跟一个间隙。

      使用宏对枚举进行运行时索引的示例(并非特别健壮 - 例如,如果枚举值可以嵌入逗号、cmets 等。您需要创建一组匹配的 &lt;[{( 和引号等..

      #include <iostream>
      #include <string>
      #include <map>
      
      namespace Benum
      {
          struct Meta
          {
              Meta(const char* p, int* p_values)
              {
                  while (*p)
                  {
                      if (isalnum(*p) || *p == '_')
                      {
                          const char* p_from = p;
                          while (isalnum(*p) || *p == '_')
                              ++p;
                          std::string idn = std::string(p_from, p - p_from);
                          int_to_string_[*p_values] = idn;
                          string_to_int_[idn] = *p_values;
                          ++p_values;
                      }
                      else if (*p == '=')
                          while (*p && *p != ',')
                              ++p;
                      else
                          ++p;
                  }
              }
              std::ostream& out(std::ostream& os, int i) const
              {
                  Int_To_String::const_iterator it = int_to_string_.find(i);
                  if (it != int_to_string_.end())
                      return os << it->second;
                  else
                      return os << "<unmatched enum " << i << '>';
              }
              typedef std::map<int, std::string> Int_To_String;
              std::map<int, std::string> int_to_string_;
              std::map<std::string, int> string_to_int_;
          };
      
          template <typename T>
          struct Incrementing
          {
              Incrementing(int n) : n_(n) { s_next_implicit_ = n + 1; }
              Incrementing() : n_(s_next_implicit_++) { }
              operator int() const { return n_; }
              int n_;
              static int s_next_implicit_;
          };
      
          template <typename T>
          int Incrementing<T>::s_next_implicit_;
      }
      
      #define BENUM(IDN, ...) \
          enum IDN ## _Enum { __VA_ARGS__ }; \
          struct IDN { \
              typedef IDN ## _Enum Enum; \
              IDN(Enum e) : e_(e) { } \
              IDN& operator=(Enum e) { e_ = e; return *this; } \
              operator Enum() const { return e_; } \
              friend std::ostream& operator<<(std::ostream& os, Enum e) { \
                  return IDN::meta().out(os, e); \
              } \
              static const Benum::Meta& meta() { \
                  static Benum::Incrementing<IDN> __VA_ARGS__; \
                  static int values[] = { __VA_ARGS__ }; \
                  static Benum::Meta m(#__VA_ARGS__, values); \
                  return m; \
              } \
              Enum e_; \
          };
      
      // benum example usage...
      
      BENUM(My_Enum, One = 1, Two = 2, Three = 3, Four = 4, Whats_Next);
      
      int main()
      {
          std::cout << One << ' ' << Two << ' ' << Three << ' ' <<
              Whats_Next << '\n';
          const Benum::Meta& meta = My_Enum::meta();
          for (std::map<int, std::string>::const_iterator i = meta.int_to_string_.begin();
              i != meta.int_to_string_.end(); ++i)
              std::cout << i->first << ' ' << i->second << '\n';
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多