2013-04-16 31 views
7

Câu hỏi này rất có thể là bản lặp thứ n số "How to map strings to enums".Kiểm tra thời gian biên dịch rằng chuỗi thành bản đồ enum hoàn thành

Yêu cầu của tôi đi xa hơn một chút và tôi muốn throw một ngoại lệ nhất định khi không tìm thấy khóa trong phạm vi các mục nhập hợp lệ. Vì vậy, tôi có thực hiện này này EnumMap (nhu cầu đẩy mạnh cho const std::map định nghĩa):

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

này hoạt động tốt và làm những gì tôi cần. Bây giờ, tôi muốn làm cho nó có thể biết tại thời gian biên dịch rằng tất cả các phần tử trong một số enum nhất định được chuyển đến hàm tạo EnumMap và cũng tất cả các phần tử trong enum được đối sánh với chuỗi tương ứng.

Tôi đã thử với std::initializer_liststatic_assert, nhưng có vẻ như VC2010 vẫn không hỗ trợ std::initializer_list (xem here).

Có ai có ý tưởng về cách thực hiện điều này không? Có lẽ với các mẫu, hoặc thực hiện lớp Enum của riêng tôi?

+1

Bạn kiểm tra mã của bạn trước khi vận chuyển, phải không? Vì vậy, kiểm tra thời gian chạy sẽ là đủ để đảm bảo nó hoạt động, bạn có nghĩ vậy không? –

+0

Cấu trúc ngôn ngữ duy nhất mà tôi biết sẽ cảnh báo bạn nếu bạn bỏ lỡ một giá trị enum là 'switch' nếu bạn thêm' case' cho mỗi giá trị và bạn không có nhánh 'default'. Tôi đoán điều này có nghĩa là bạn sẽ cần một vĩ mô nhưng tôi xa đề xuất bất cứ điều gì cụ thể :) –

+0

Tôi đồng ý rằng thời gian chạy thử nghiệm là đủ, và tôi cũng biết về cảnh báo. Tôi nghĩ rằng tôi chỉ cố gắng tìm ra cách xa này có thể được đẩy ra – FKaria

Trả lời

2

Có ai có ý tưởng về cách thực hiện điều này không? Có lẽ với các mẫu, hoặc thực hiện lớp Enum của riêng tôi?

Không thể thực hiện được. Không phải với std :: map, và không phải với template meta-programming.

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

mang đến cho bạn một cách C++ 11 để xây dựng một std::map từ thời gian biên dịch color và thời gian chạy string dữ liệu.

Ném vào "danh sách MapEntry<colors> vào danh sách colors" tính năng tiếp theo.

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

Sắp xếp danh sách đó. Phát hiện các bản sao - nếu có, bạn đã làm hỏng.

Sắp xếp thời gian biên dịch khó hơn phần còn lại của điều này và hơn 100 dòng mã. Tôi sẽ bỏ nó ra nếu bạn không nhớ quá nhiều! Here is a compile time merge sort I wrote in the past to answer a stack overflow question sẽ hoạt động với sự thích nghi tương đối đơn giản (nó sắp xếp các loại với các giá trị, trong trường hợp này chúng tôi sắp xếp một danh sách các giá trị thời gian biên dịch trực tiếp).

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

Phát hiện các yếu tố bên ngoài phạm vi hợp lệ colors (ví dụ, <MIN_COLOR hoặc >=MAX_COLOR). Nếu vậy, bạn đã bị hỏng.

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

Đếm số lượng phần tử - cần có MAX_COLOR yếu tố. Nếu không, bạn hơi say.

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

Nếu không ai trong số này xảy ra, bởi pigeonhole bạn phải đã khởi tạo mỗi người trong số họ.

Điều duy nhất còn thiếu là bạn có thể đã tắt và sử dụng cùng một string cho hai giá trị.Khi biên dịch thời gian string s là một nỗi đau, chỉ cần kiểm tra điều này tại thời gian chạy (số lượng các mục trong map bằng số lượng colors sau khi bạn khởi tạo nó).

Làm điều này trong C++ 03 sẽ khó hơn. Bạn thiếu các mẫu biến dạng, vì vậy bạn sẽ phải giả mạo chúng. Đó là một nỗi đau. mpl có thể giúp bạn ở đó.

Mẫu biến thể có sẵn trong bản cập nhật trình biên dịch MSVC CTP tháng 11 năm 2012.

Dưới đây là ví dụ về đồ chơi mà không cần kiểm tra trùng lặp và không kiểm tra giới hạn (nó chỉ kiểm tra số lượng mục khớp với bản đồ);

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

Thay thế #if 0 bằng #if 1 để xem nó biên dịch. Live link nếu bạn muốn chơi.

+0

bạn không hiển thị cách sử dụng nó để có lỗi biên dịch –

+0

'static_assert (khoản," mọi thứ không tốt ")' là cách C++ 11 để nhận lỗi biên dịch. Hoặc bạn có nghĩa là phân loại thời gian biên dịch và kiểm tra tính độc đáo, kiểm tra giới hạn, và các lớp học đặc điểm tương tự? – Yakk

+0

Câu trả lời hay. Có vẻ như điều này sẽ chỉ làm việc cho một loại 'enum' cụ thể, cụ thể là' màu sắc'. Dường như điều này có thể càng gần càng tốt để đạt được những gì tôi đang cố gắng làm, và không có giải pháp đơn giản nào có thể xảy ra vì bạn đang sử dụng rất nhiều wapon nặng. Tôi nghĩ rằng sự thay thế duy nhất khác sẽ là thực hiện một lớp 'Enum' tùy chỉnh và' EnumMap' tùy chỉnh chứa các phần tử của nó. – FKaria

0

Tôi muốn đăng giải pháp mà tôi đã đến cuối cùng. Tôi không muốn đánh dấu nó như là một câu trả lời dứt khoát bởi vì tôi muốn tìm hiểu xem chúng ta có thể định nghĩa vectơ của chuỗi tại thời gian biên dịch hay không.

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

Run nó trên Coliru: http://coliru.stacked-crooked.com/a/e81e1af0145df99a

+0

xấu của bạn Bạn có thể biến nó thành thời gian biên dịch. Xem [tại đây] (http://stackoverflow.com/questions/28828957/enum-to-string-in-modern-c-and-future-c17/31362042#31362042). Có một biến thể của giải pháp đó với thời gian biên dịch 'to_string'. – antron

+0

Cảm ơn @antron. Tôi cũng tìm thấy một cách để xác định các vector tại thời gian biên dịch bằng cách sử dụng thư viện tiền xử lý tăng cường nhưng tôi chỉ không cập nhật câu trả lời này. Tôi vừa mới đăng giải pháp của tôi trong [chủ đề đó] (http://stackoverflow.com/a/35788543/1405424). Tôi tin rằng là tương tự như giải pháp của bạn, ngoại trừ việc nó sử dụng tiền xử lý tăng cường cho các macro. Tôi cũng bỏ qua chuỗi -> chuyển đổi enum bởi vì câu hỏi chỉ yêu cầu enum -> string. – FKaria

Các vấn đề liên quan