2013-04-16 160 views
7

這個問題很可能是"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類?

+1

你在出廠前測試你的代碼,對不對?因此,運行時檢查足以確保它能夠工作,您不覺得嗎? –

+0

如果您爲每個值添加一個「case」並且您沒有「默認」分支,那麼如果您錯過枚舉值,那麼我知道的唯一語言構造會警告您。我想這意味着你將需要一個宏,但我遠沒有建議任何具體的:) –

+0

我同意運行時測試應該足夠了,我也知道有關警告。我想我只是想弄清楚這可以推出多遠 – FKaria

回答

2

有沒有人有關於如何實現它的想法?也許有模板,或者實現我自己的Enum類?

這是不可能的。不適用於std :: map,不適用於模板元編程。

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

,讓你從編譯時color構建std::map和運行時string數據的C++ 11的方式。

接下來在「列表MapEntry<colors>列表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(即,<MIN_COLOR>=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。由於編譯時間string是一個痛苦,只需在運行時檢查它(在初始化之後,map中的條目數等於colors的數目)。

在C++ 03中這樣做會更困難。你缺乏variardic模板,所以你最終不得不僞造它們。這是一個痛苦。 mpl可能可以幫助您。

2012年11月MSVC CTP編譯器更新中提供了Variardic模板。

這是一個沒有重複檢查和沒有邊界檢查的玩具示例(它只是檢查地圖項的數量是否匹配);

#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如果你想玩。

+0

你沒有演示如何使用它來獲得編譯錯誤 –

+0

'static_assert(clause,「things are no good」)''是得到編譯錯誤的C++ 11方法。還是你的意思是編譯時排序和唯一性測試,邊界檢查等特質類? – Yakk

+0

很好的回答。看起來這隻適用於特定的'enum'類型,即'colors'。看起來這可以儘可能地接近我實現的目標,並且由於您使用了大量重磅武器,所以沒有更簡單的解決方案。我在想唯一的另一種方法是實現一個自定義的'Enum'類和自定義的'EnumMap'來包含它的元素。 – FKaria

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

+0

你可以讓它編譯時。請參閱[這裏](http://stackoverflow.com/questions/28828957/enum-to-string-in-modern-c-and-future-c17/31362042#31362042)。該解決方案也有編譯時'to_string'的變體。 – antron

+0

謝謝@antron。我還發現了一種在編譯時使用Boost預處理器庫來定義向量的方法,但我沒有更新這個答案。我剛剛在[該線程](http://stackoverflow.com/a/35788543/1405424)中發佈了我的解決方案。我相信它與您的解決方案類似,只不過它使用宏的Boost預處理器。我也省略了字符串 - >枚舉轉換,因爲這個問題只會詢問枚舉 - >字符串。 – FKaria