7

This question may very well be the n-th iteration of "How to map strings to enums".

My requirements go a little bit further and I want to throw a certain exception when a key is not found in the range of valid inputs. So I have this implementation of this EnumMap (needs boost for const std::map definition):

#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;
}

This works well and does what I need. Now, I want to make it possible to know at compile time that all the elements in a certain enum are passed to the EnumMap constructor, and also that all the elements in the enum are matched with the corresponding string.

I tried with std::initializer_list and static_assert, but it seems that VC2010 does not still support std::initializer_list (see here).

Does anyone have an idea on how it would be possible to implement this? Perhaps with templates, or implementing my own Enum class?

4

3 回答 3

4
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") );

which gives you a C++11 way to construct a std::map from compile time color and run-time string data.

Throw in a "list of MapEntry<colors> to list of colors" metafunction next.

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;
};

Sort that list. Detect duplicates -- if there are, you screwed up.

Compile-time sorting is harder than the rest of this, and a 100+ lines of code. I'll leave it out if you don't mind too much! Here is a compile time merge sort I wrote in the past to answer a stack overflow question that would work with relatively simple adaptation (it sorts types with values, in this case we are sorting a list of compile-time values directly).

// 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...> > {};

Detect elements outside of the valid range of colors (ie, <MIN_COLOR or >=MAX_COLOR). If so, you screwed up.

 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)
 > {};

Count how many elements there are -- there should be MAX_COLOR elements. If not, you screwed up.

 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) > {};

If none of these occurred, by pigeonhole you must have initialized each of them.

The only thing missing is that you could have gone off and used the same string for two values. As compile time strings are a pain, just check this at run time (that the number of entries in the map equals the number of colors after you initialize it).

Doing this in C++03 will be harder. You lack variardic templates, so you end up having to fake them. Which is a pain. mpl might be able to help you there.

Variardic templates are available in the Nov 2012 MSVC CTP compiler update.

Here is a toy example without duplicate checking and without bounds checking (it just checks that the number of map entries matches);

#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() {}

Replace the #if 0 with #if 1 to watch it compile. Live link if you want to play.

于 2013-04-16T21:11:40.607 回答
2

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

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

于 2013-04-16T20:15:05.697 回答
0

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

// 
//  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

于 2016-01-08T18:08:04.657 回答