【问题标题】:Compile time check that string to enum map is complete编译时检查字符串到枚举映射是否完整
【发布时间】:2013-04-09 08:54:11
【问题描述】:

这个问题很可能是"How to map strings to enums" 的第n 次迭代。

我的要求更进一步,当在有效输入范围内找不到键时,我想throw 某个异常。所以我有这个EnumMap 的实现(需要提升const std::map 的定义):

#include <map>
#include <string>
#include <sstream>
#include <stdexcept>
#include <boost/assign.hpp>

typedef enum colors {
  RED,
  GREEN,
} colors;
// boost::assign::map_list_of
const std::map<std::string,int> colorsMap  = boost::assign::map_list_of
                                            ("red",   RED)
                                            ("green", GREEN);
//-----------------------------------------------------------------------------
// wrapper for a map std::string --> enum
class EnumMap {
 private:
  std::map<std::string,int> m_map;
  // print the map to a string
  std::string toString() const {
    std::string ostr;
    for(auto x : m_map) {
      ostr += x.first + ", ";
    }
    return ostr;
  }
 public:
  // constructor
  EnumMap(const std::map<std::string,int> &m) : m_map(m) { }
  // access
  int at(const std::string &str_type) {
    try{
      return m_map.at(str_type);
    }
    catch(std::out_of_range) {
      throw(str_type + " is not a valid input, try : " + toString());
    }
    catch(...) {
      throw("Unknown exception");
    }
  }
};
//-----------------------------------------------------------------------------
int main()
{
  EnumMap aColorMap(colorsMap);
  try {
    aColorMap.at("red");    // ok
    aColorMap.at("yellow"); // exception : "yellow is not a valid input ..."
  }
  catch(std::string &ex) {
    std::cout << ex << std::endl;
  }
  return 0;
}

这很好用,可以满足我的需要。现在,我想在编译时知道某个enum 中的所有元素都传递给EnumMap 构造函数,并且enum 中的所有元素都与相应的字符串匹配。

我试过std::initializer_liststatic_assert,但VC2010似乎还不支持std::initializer_list(见here)。

有没有人知道如何实现这一点?也许使用模板,或者实现我自己的 Enum 类?

【问题讨论】:

  • 您会在发货前测试您的代码,对吗?所以运行时检查就足以确保它确实有效,你不觉得吗?
  • 如果您为每个值添加case 而您没有default -分支。我想这意味着你需要一个宏,但我并没有提出任何具体的建议:)
  • 我同意运行时测试就足够了,而且我也知道警告。我想我只是想弄清楚这可以推到多远
  • 上述代码具有未定义的行为,因为您在全局范围内有一个以_ 开头的标识符,并且下一个字符是大写字母。 (又名_RED)。
  • C++ reserved word list 在 cpp 参考。靠近底部的段落包括“每个包含双下划线 __ 或以下划线后跟大写字母的名称始终保留给实现,不应用作标识符。以下划线开头的每个名称都保留给在全局命名空间中用作名称的实现”。

标签: c++ string map c++11 enums


【解决方案1】:
typedef enum colors {
  MIN_COLOR,
  RED = MIN_COLOR,
  GREEN,
  MAX_COLOR
} colors;

template< colors C >
struct MapEntry {
  std::string s;
  MapEntry(std::string s_):s(s_) {}
};
void do_in_order() {}
template<typename F0, typename... Fs>
void do_in_order(F0&& f0, Fs&&... fs) {
  std::forward<F0>(f0)();
  do_in_order( std::forward<Fs>(fs)... );
}
struct MapInit {
  std::map< std::string, color > retval;
  operator std::map< std::string, color >() {
    return std::move(retval);
  }
  template<colors C>
  void AddToMap( MapEntry<C>&& ent ) {
    retval.insert( std::make_pair( std::move(end.s), C ) );
  }
  template< typename... Entries >
  MapInit( Entries&& entries ) {
    do_in_order([&](){ AddToMap(entries); }...);
  } 
};
template<typename... Entries>
MapInit mapInit( Entries&&... entries ) {
  return MapInit( std::forward<Entries>(entries)... );
}
const std::map<std::string, colors> = mapInit( MapEntry<RED>("red"), MapEntry<GREEN>("green") );

它为您提供了一种 C++11 方法,可以从编译时 color 和运行时 string 数据构造 std::map

接下来将“MapEntry&lt;colors&gt; 列表添加到colors 列表”元函数中。

template<colors... Cs>
struct color_list {};
template<typename... Ts>
struct type_list {};
template<typename MapEnt>
struct extract_color;
template<colors C>
struct extract_color<MapEntry<C>> {
  enum {value=C};
};
template<typename Entries>
struct extract_colors;
template<typename... MapEntries>
struct extract_colors<type_list<MapEntries...>> {
  typedef color_list< ( (colors)extract_colors<MapEntries>::value)... > type;
};

对该列表进行排序。检测重复 - 如果有,你搞砸了。

编译时排序比其余部分更难,而且要超过 100 行以上的代码。如果你不介意的话,我会忽略它! Here is a compile time merge sort I wrote in the past to answer a stack overflow question 可以通过相对简单的适配工作(它使用值对类型进行排序,在这种情况下,我们直接对编译时值列表进行排序)。

// takes a sorted list of type L<T...>, returns true if there are adjacent equal
// elements:
template<typename clist, typename=void>
struct any_duplicates:std::false_type {};
template<typename T, template<T...>class L, T t0, T t1, T... ts>
struct any_duplicates< L<t0, t1, ts...>, typename std::enable_if<t0==t1>::type>:
  std::true_type {};
template<typename T, template<T...>class L, T t0, T t1, T... ts>
struct any_duplicates< L<t0, t1, ts...>, typename std::enable_if<t0!=t1>::type>:
  any_duplicates< L<t1, ts...> > {};

检测colors 有效范围之外的元素(即&lt;MIN_COLOR&gt;=MAX_COLOR)。如果是这样,你搞砸了。

 template<typename List>
 struct min_max;
 template<typename T, template<T...>class L, T t0>
 struct min_max {
   enum {
     min = t0,
     max = t1,
   };
 };
 template<typename T, template<T...>class L, T t0, T t1, T... ts>
 struct min_max {
   typedef min_max<L<t1, ts...>> rest_of_list;
   enum {
     rest_min = rest_of_list::min,
     rest_max = rest_of_list::max,
     min = (rest_min < t0):rest_min:t0,
     max = (rest_max > t0):rest_max:t0,
   };
 };
 template< typename T, T min, T max, typename List >
 struct bounded: std::integral_constant< bool,
   (min_max<List>::min >= min) && (min_max<List>::max < max)
 > {};

计算有多少元素——应该有MAX_COLOR 元素。如果没有,你搞砸了。

 template<typename List>
 struct element_count;
 template<typename T, template<T...>L, T... ts>
 struct element_count<L<ts...>>:std::integral_constant< std::size_t, sizeof...(ts) > {};

如果这些都没有发生,那么您一定已经初始化了它们中的每一个。

唯一缺少的是您可以离开并为两个值使用相同的string。由于编译时strings 很麻烦,只需在运行时检查它(map 中的条目数等于初始化后colors 的数量)。

在 C++03 中这样做会更难。您缺少可变参数模板,因此您最终不得不伪造它们。这是一种痛苦。 mpl 或许能帮到你。

2012 年 11 月的 MSVC CTP 编译器更新中提供了可变参数模板。

这是一个没有重复检查和没有边界检查的玩具示例(它只是检查地图条目的数量是否匹配);

#include <cstddef>
#include <utility>
#include <string>
#include <map>

enum TestEnum {
   BeginVal = 0,
   One = BeginVal,
   Two,
   Three,
   EndVal
};

template<TestEnum e>
struct MapEntry {
  enum { val = e };
  std::string s;
  MapEntry( std::string s_ ):s(s_) {}
};

void do_in_order() {}
template<typename F0, typename... Fs>
void do_in_order(F0&& f0, Fs&&... fs) {
  std::forward<F0>(f0)();
  do_in_order( std::forward<Fs>(fs)... );
}

template<typename... MapEntries>
struct count_entries:std::integral_constant< std::size_t, sizeof...(MapEntries) > {};

// should also detect duplicates and check the range of the values:
template<typename... MapEntries>
struct caught_them_all:
  std::integral_constant<
    bool,
    count_entries<MapEntries...>::value == (TestEnum::EndVal-TestEnum::BeginVal)
  >
{};

struct BuildMap {
  typedef std::map<std::string, TestEnum> result_map;
  mutable result_map val;
  operator result_map() const {
    return std::move(val);
  }
  template<typename... MapEntries>
  BuildMap( MapEntries&&... entries ) {
    static_assert( caught_them_all<MapEntries...>::value, "Missing enum value" );
    bool _[] = { ( (val[ entries.s ] = TestEnum(MapEntries::val)), false )... };
  }
};

std::map< std::string, TestEnum > bob = BuildMap(
  MapEntry<One>("One")
  ,MapEntry<Two>("Two")
#if 0
  ,MapEntry<Three>("Three")
#endif
);

int main() {}

#if 0 替换为#if 1 以观察它的编译。 Live link如果你想玩。

【讨论】:

  • static_assert( clause, "things are no good" ) 是 C++11 获取编译错误的方法。或者您的意思是编译时排序和唯一性测试、边界检查和类似的特征类?
  • 很好的答案。似乎这只适用于特定的enum 类型,即colors。似乎这可以尽可能接近实现我想要做的事情,并且似乎没有更简单的解决方案,因为您使用了很多重型武器。我认为唯一的另一种选择是实现自定义 Enum 类和自定义 EnumMap 以包含其元素。
  • @FkKria 从理论上讲,您可以通过谈论 E::begin_valE::end_val 类型为 E 传递的任何“看起来像”enum bob { begin_val=0, ..., end_val }enum 使其工作更进一步,特征类template&lt;typename E&gt; struct begin_valend_val 可以让您以非侵入方式对具有相邻值的任何enum 执行此操作,并根据需要进行专门化。更进一步,定义了您的enum 定义的有效范围集合的特征类可用于检查您是否“全部捕获”。
  • @FKaria 最后,您可以创建作为索引类型集合的伪枚举,可能在伪枚举 (typedef PseudoEnum&lt; struct Red, struct Green, struct Blue &gt; Colors) 甚至typedef PseudoEnum&lt; std::pair&lt;struct Red, 0&gt;, std::pair&lt;struct Green,7&gt;, std::pair&lt;struct Blue,9&gt; &gt; Colors 的定义中引入,访问权限如下Colors::Val&lt;Blue&gt;(),其中第一个组件定义了一个标签,第二个组件定义了一个有效的,然后您将拥有PseudoEnum 结构的完整编译时反射。如果boost::mpl 还没有这样的伪枚举,我会感到惊讶。
  • 我想我误解了这个问题。我认为这应该使编译失败:std::cout&lt;&lt;bob["lala"]&lt;&lt;std::endl;
【解决方案2】:

有没有人知道如何实现这一点?也许使用模板,或者实现我自己的 Enum 类?

这是不可能的。不适用于 std::map,也不适用于模板元编程。

【讨论】:

    【解决方案3】:

    我想发布我最终得到的解决方案。我不想将其标记为明确的答案,因为我想弄清楚我们是否可以在编译时定义字符串向量。

    // 
    //  Cumstom Enum Header File
    //
    
    #include <vector>
    #include <string>
    
    #include <boost/algorithm/string/classification.hpp> 
    #include <boost/algorithm/string/split.hpp>
    #include <boost/range/algorithm/find.hpp>
    
    std::vector<std::string> split_to_vector(std::string s)
    {
        // splits a comma separated string to a vector of strings
        std::vector<std::string> v;
        boost::split(v, s, boost::is_any_of(", "), boost::token_compress_on);
        return v;
    }
    
    #define ENUM_TO_STRING(X,...) \
        struct X {  \
          enum Enum {__VA_ARGS__};  \
            static  \
            std::string to_string(X::Enum k) {  \
                std::vector<std::string> keys = split_to_vector(#__VA_ARGS__);  \
                return keys[static_cast<int>(k)];   \
            }   \
            static  \
            X::Enum which_one(const std::string s) {    \
                std::vector<std::string> keys = split_to_vector(#__VA_ARGS__);  \
                auto it = boost::find(keys, s); \
                if(it == keys.end()) {  \
                    throw("not a valid key");   \
                }   \
                return static_cast<X::Enum>(it - keys.begin()); \
            }   \
        }
    
    
    //
    // Usage 
    //
    
    #include <iostream>
    
    ENUM_TO_STRING(Color, Red, Green, Blue);
    
    int main()
    {
        std::string red_s = Color::to_string(Color::Red);
        std::cout << red_s << std::endl;
    
        Color::Enum red_e = Color::which_one("Red");
        std::cout << red_e << std::endl;
    
        // won't compile
        // std::string yellow_s = Colors::to_string(Colors::Yellow);
    
        // run-time error
        // Color::Enum yellow_e = Colors::which_one("Yellow");
    }
    

    在 Coliru 上运行它: http://coliru.stacked-crooked.com/a/e81e1af0145df99a

    【讨论】:

    • 你可以让它编译时。见here。该解决方案的一个变体也具有编译时to_string
    • 感谢@antron。我还找到了一种在编译时使用 Boost 预处理器库定义向量的方法,但我只是没有更新这个答案。我刚刚在that thread 中发布了我的解决方案。我相信这与您的解决方案类似,只是它为宏使用了 Boost 预处理器。我也省略了字符串 -> 枚举转换,因为问题只要求枚举 -> 字符串。
    猜你喜欢
    • 1970-01-01
    • 2021-11-09
    • 2019-01-29
    • 2012-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多