如何编写通用的“价值安全枚举”?

时间:2013-06-16 09:33:02

标签: c++11 enums compile-time

C ++ 11提供了一个改进的enumenum struct。 但是这仍然会受到肯定的影响 - 直到你习惯了它 - 劣等老enum最令人惊讶的陷阱:a的价值 enum struct E类型的变量不必是任何枚举的变量 可以是E基础积分类型范围内的任何值:

enum struct X { one = 1};
X x = X(); // Fine! The value of x is int(0)
x = X(2); // Cool! The value of x is int(2)

您使用enumenum struct类型E的对象 必须抓住“不是E s”之一的情况。

如何定义可以使用的通用类型 代替enum struct(不一定是替补) 具有实例化类的对象不能的属性 假设“枚举”之外的值?

我的意思是不能在某种意义上,如果对象会抛出,那么就会感到满意 假设任何不可区分的值(即 it 捕获案例 它将变成“不是E s之一”。

我在吓唬报价中说“列举”因为它似乎不可避免 (不使用宏)这些值将被“枚举” 通过一系列整数类型模板参数而不能 访问方便X::one

如果这是不可避免的,只要是“枚举”值就可以了 通过类型的枚举索引成为可静态检索的常量。 (然后,客户端代码将meangingful符号与其中任何一个相关联将很简单 索引或索引值,并封装这样方便 映射 - 例如在struct - 嵌套的匿名enum!)

对于这个问题我是否已经有一个备受好评的解决方案 知道吗?

通过提交者请求(阿里)

继续

  

你能发一些伪代码吗?它应该显示你想如何使用它。

以下是所需用法的一些迹象(我认为):

/* 1 */
/*  These 2 structs could just be tag-types X{}, Y{}
    serving to distinguish value-safe-enums that
    would otherwise be the same. But you could
    also get more mileage out them, as shown...
*/
struct turn;
struct hand;

using vs_turn = val_safe_enum<turn,int,1,2>;
using vs_hand = val_safe_enum<hand,int,1,2>;

struct turn
{
    // Want an anonymous scoped enum; only rvalue 
    enum {
        right = vs_turn::which<0>(), // = 1
        left = vs_turn::which<1>() // = 2
    };
};

struct hand
{
    enum {
        right = vs_hand::which<0>(), //= 1
        left = vs_hand::which<1>() // = 2
    };
};
/* End 1 */

/* 2 */
char const * foo(vs_turn const & t) {
    // Only 2 possibilities!
    return int(t) == turn::right ? "right turn" : "left turn";
}
char const * foo(vs_hand const & h) {
    return int(h) == hand::right ? "right hand" : "left hand";
} 
/* End 2 */  


vs_turn t1(0); // 3.a OK
vs_turn t2(turn::right); // 3b. OK
vs_hand h1(hand::left); // 3c. OK
vs_hand h2(1); // 3d. OK

t1 == vs_turn(turn::right); // 4. OK

t1 < t2; // 5. OK

t1 = t2; // 6. OK

int(t1) == turn::right; // 7. OK. Explicit conversion to underlying type.

/* 8 */ 
switch(int(t1)) {
case turn::right:
    /* Something */ break;
case turn::left:
    /* Something */;
    // No default necessary! 
}
/* End 8 */

vs_turn t3(3); // 9. Throw! Value out of range
vs_turn t4; // 10. Error. No default construction.

t1 == turn::right; // 11a. Error. No Conversion
t1 <= h1; // 11b. Error. No conversion.
t1 = turn::right; // 11c. Error. No conversion
t1 = h1; // 11d. Error. No conversion.
foo(turn::right); // 11e. Error. No conversion

7 个答案:

答案 0 :(得分:1)

struct C
{
    enum E { a, b, c };

    C(E e) : e(e) { if (e > c) throw logic_error; }

private:
    E e;
};

<强>更新

template<typename T, T... E> struct Check;

template<typename T> struct Check<T> { void check(T) { throw logic_error; } }

template<typename T, T e0, T... E> struct Check<T, e0, E...>
{
    void check(T e)
    {
        if (e != e0)
            Check<T, E>::check(e);
    }
}

template<typename T, T... E>
struct C
{
    C(T e) : e(e) { Check<T, E...>::check(e); }

private:
    T e;
}

答案 1 :(得分:0)

这个怎么样?

struct MyColor {
public:
    static MyColor Red; // defined as MyColor(red)
    static MyColor Blue; // defined as MyColor(blue)

    // Copy constructors, assignment etc. goes here
private:
    MyColor(Value value);
    enum Value { red, blue, green };
    Value value;
};

答案 2 :(得分:0)

它很丑,但在编译时失败了:

enum E { a, b, c };

constexpr E range_check(E e) {
   return e <= c ? e : (throw "enum value out of range"); // fails at compile time, see below
}

class wrapper {
  public:
    constexpr wrapper(E e) : e(range_check(e)) { }
  private:
    enum E e;
};

int main() {
  constexpr wrapper w((E)42);
}

我不喜欢这种方法,因为错误信息没有帮助。然而,it is guaranteed在编译时失败。

这是你在找什么?

答案 3 :(得分:0)

在过去的一周里,我一直在寻找一种方法来做你想要的,等等。 以下是我提出的建议:

#define STD_ENUM_ENTRY_WITHOUT_VALUE__(ENUM, NAME)      NAME,
#define STD_ENUM_ENTRY_WITHOUT_VALUE_(ENUM, NAME)       STD_ENUM_ENTRY_WITHOUT_VALUE__(ENUM, NAME)
#define STD_ENUM_ENTRY_WITHOUT_VALUE(ENUM, SPLIT...)    STD_ENUM_ENTRY_WITHOUT_VALUE_(ENUM, SPLIT)

#define STD_ENUM_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)  NAME = VALUE,
#define STD_ENUM_ENTRY_WITH_VALUE_(ENUM, NAME, VALUE)   STD_ENUM_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)
#define STD_ENUM_ENTRY_WITH_VALUE(ENUM, SPLIT...)       STD_ENUM_ENTRY_WITH_VALUE_(ENUM, SPLIT)

#define FP_PP_ENUM_STD_ENTRY_1(ENUM, SPLIT...)          STD_ENUM_ENTRY_WITHOUT_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_STD_ENTRY_2(ENUM, SPLIT...)          STD_ENUM_ENTRY_WITH_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_STD_ENTRY__(N, ENUM, SPLIT...)       FP_PP_ENUM_STD_ENTRY_##N(ENUM, SPLIT)
#define FP_PP_ENUM_STD_ENTRY_(N, ENUM, VALUE)           FP_PP_ENUM_STD_ENTRY__(N, ENUM, FP_PP_EXPAND VALUE)
#define FP_PP_ENUM_STD_ENTRY(ENUM, VALUE)               FP_PP_ENUM_STD_ENTRY_(FP_PP_NUM_ARGS VALUE, ENUM, VALUE)

#define FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE__(ENUM, NAME)      ::fp::enum_entry<ENUM>(ENUM::NAME, #NAME),
#define FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE_(ENUM, NAME)       FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE__(ENUM, NAME)
#define FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE(ENUM, SPLIT...)    FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE_(ENUM, SPLIT)

#define FP_PP_ENUM_EXT_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)  ::fp::enum_entry<ENUM>(ENUM::NAME, #NAME),
#define FP_PP_ENUM_EXT_ENTRY_WITH_VALUE_(ENUM, NAME, VALUE)   FP_PP_ENUM_EXT_ENTRY_WITH_VALUE__(ENUM, NAME, VALUE)
#define FP_PP_ENUM_EXT_ENTRY_WITH_VALUE(ENUM, SPLIT...)       FP_PP_ENUM_EXT_ENTRY_WITH_VALUE_(ENUM, SPLIT)

#define FP_PP_ENUM_EXT_ENTRY_1(ENUM, SPLIT...)          FP_PP_ENUM_EXT_ENTRY_WITHOUT_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_EXT_ENTRY_2(ENUM, SPLIT...)          FP_PP_ENUM_EXT_ENTRY_WITH_VALUE(ENUM, SPLIT)
#define FP_PP_ENUM_EXT_ENTRY__(N, ENUM, SPLIT...)       FP_PP_ENUM_EXT_ENTRY_##N(ENUM, SPLIT)
#define FP_PP_ENUM_EXT_ENTRY_(N, ENUM, VALUE)           FP_PP_ENUM_EXT_ENTRY__(N, ENUM, FP_PP_EXPAND VALUE)
#define FP_PP_ENUM_EXT_ENTRY(ENUM, VALUE)               FP_PP_ENUM_EXT_ENTRY_(FP_PP_NUM_ARGS VALUE, ENUM, VALUE)

#define DEFINE_EXT_ENUM(ENUM, ...)                                                          \
    enum class ENUM {                                                                       \
        FP_PP_SEQ_FOR_EACH(FP_PP_ENUM_STD_ENTRY, ENUM, __VA_ARGS__)                         \
    };                                                                                      \
    template<typename>                                                                      \
    struct enum_descriptor;                                                                 \
                                                                                            \
    template<>                                                                              \
    struct enum_descriptor<ENUM> {                                                          \
    public:                                                                                 \
        using enum_type = ENUM;                                                             \
        using entry_type = ::fp::enum_entry<enum_type>;                                     \
        using this_type = enum_descriptor<enum_type>;                                       \
        using const_iterator = entry_type const *;                                          \
        using const_reverse_iterator = std::reverse_iterator<const_iterator>;               \
        using size_type = std::size_t;                                                      \
    private:                                                                                \
        constexpr static std::size_t Size = FP_PP_NUM_ARGS(__VA_ARGS__);                    \
        using container_type = std::array<entry_type, Size>;                                \
                                                                                            \
        constexpr static container_type const _entries                                      \
        {                                                                                   \
            {                                                                               \
                FP_PP_SEQ_FOR_EACH(FP_PP_ENUM_EXT_ENTRY, ENUM, __VA_ARGS__)                 \
            }                                                                               \
        };                                                                                  \
                                                                                            \
        template<std::size_t... Is >                                                        \
        constexpr static char const * name_of_impl(enum_type v, ::fp::indices<Is...>) {     \
            using std::get;                                                                 \
            return ::fp::enum_helper<enum_type>::get_name(v, get<Is>(_entries)...);         \
        }                                                                                   \
                                                                                            \
        template<std::size_t... Is >                                                        \
        constexpr static enum_type value_of_impl(char const * n, ::fp::indices<Is...>) {    \
            using std::get;                                                                 \
            return ::fp::enum_helper<enum_type>::get_value(n, get<Is>(_entries)...);        \
        }                                                                                   \
                                                                                            \
        template<typename V, std::size_t... Is >                                            \
        static bool try_parse_impl(V val, enum_type & res, ::fp::indices<Is...>) {          \
            using std::get;                                                                 \
            return (::fp::enum_helper<enum_type>::is_valid_entry(val, get<Is>(_entries)...)) ? \
                ((res = static_cast<enum_type> (val)), true)                                \
                : false;                                                                    \
        }                                                                                   \
                                                                                            \
        template<typename V, std::size_t... Is >                                            \
        constexpr static enum_type parse_impl(V val, ::fp::indices<Is...>) {              \
            using std::get;                                                                 \
            return (::fp::enum_helper<enum_type>::parse(val, get<Is>(_entries)...));        \
        }                                                                                   \
    public:                                                                                 \
        constexpr enum_descriptor() = default;                                              \
        enum_descriptor(enum_descriptor const &) = delete;                                  \
        enum_descriptor(enum_descriptor &&) = delete;                                       \
                                                                                            \
        constexpr static char const * name() noexcept {                                     \
            return #ENUM;                                                                   \
        }                                                                                   \
                                                                                            \
        constexpr static char const * name_of(enum_type value) {                            \
            return name_of_impl(value, ::fp::build_indices<Size>());                        \
        }                                                                                   \
                                                                                            \
        constexpr static enum_type value_of(char const * name) {                            \
            return value_of_impl(name, ::fp::build_indices<Size>());                        \
        }                                                                                   \
                                                                                            \
        constexpr static size_type size() noexcept {                                        \
            return Size;                                                                    \
        }                                                                                   \
                                                                                            \
        constexpr static const_iterator begin() {                                           \
            using std::get;                                                                 \
            return const_iterator(&get<0>(_entries));                                       \
        }                                                                                   \
                                                                                            \
        constexpr static const_iterator end() {                                             \
            using std::get;                                                                 \
            return const_iterator(&get<(Size - 1)>(_entries) + 1);                          \
        }                                                                                   \
                                                                                            \
        template<typename T,                                                                \
                typename = typename std::enable_if<std::is_integral<T>::value>::type>       \
        static bool try_parse(T value, enum_type & res){                                    \
            return try_parse_impl(value, res, ::fp::build_indices<Size>());                 \
        }                                                                                   \
                                                                                            \
        template<typename T,                                                                \
                typename = typename std::enable_if<std::is_integral<T>::value>::type>       \
        constexpr static enum_type parse(T value){                                          \
            return parse_impl(value, ::fp::build_indices<Size>());                          \
        }                                                                                   \
    };                                                                                      \
    template<>                                                                              \
    constexpr std::array<::fp::enum_entry<ENUM>, FP_PP_NUM_ARGS(__VA_ARGS__)> const enum_descriptor<ENUM>::_entries;

您可以在我的github上查看完整代码(包括示例)。

虽然这可以达到我的预期,但不能(直接)用作现有枚举的替代品。

为了节省大量编码,您需要支持的枚举应该如下定义:

DEFINE_EXT_ENUM(my_1st_enum, (fread, 3), (fwrite), (fflush, fread << 2));
DEFINE_EXT_ENUM(my_2nd_enum, (fopen), (fclose, 1));

最后一件事:不要尝试使用-Werr标志在GCC上编译它,因为编译会出错(我理解为什么,但我目前不知道如何解决它)。

编辑:

根据您的示例,您可以使用我的DEFINE_EXT_ENUM进行操作:

#include "enum_pp_def.hpp"

#include <cassert>
#include <iostream>

DEFINE_EXT_ENUM(turn, (right,1), (left,2));
DEFINE_EXT_ENUM(hand, (right,1), (left,2));

using turn_descr = enum_descriptor<turn>;
using hand_descr = enum_descriptor<hand>;

/* 2 */
constexpr char const * foo(turn t) {
    return (turn_descr::is_valid((int)t)) ? (turn::right == t) ? "right turn" : "left turn" : throw t;
}
constexpr char const * foo(hand h) {
    return (hand_descr::is_valid((int)h)) ? (hand::right == h) ? "right hand" : "left hand" : throw h;
}
/* End 2 */  

int main(int argc, char ** argv) {
    turn t1 = turn_descr::parse(1); // 3.a OK
    turn t2(turn::right); // 3b. OK
    hand h1(hand::left); // 3c. OK
    hand h2 = hand_descr::parse(2); // 3d. OK

    assert(t1 == turn::right); // 4. OK

    /* 8 */ 
    switch(t1) {
    case turn::right:
        std::cout << "right turn" << std::endl;
        break;
    case turn::left:
        std::cout << "left turn" << std::endl;
        break;
    }
    /* End 8 */

    std::cout << foo(hand::left) << std::endl;
    std::cout << foo(turn::right) << std::endl;

    constexpr turn t3 = turn_descr::parse(3)    // throw at compile time
    turn t4 = turn_descr::parse(3);             // throw at runtime
}

答案 4 :(得分:0)

我写了这个丑陋的宏来生成枚举包装(不支持C ++ 11枚举:()。 如您所见,ctor检查传递的值是否为枚举值:

#include <iostream>
using namespace std;

#define GENERATE_ENUM(NAME,VALUES...) class NAME                                         \
                                      {                                                  \
                                      private:                                           \
                                         static const int _ENUM_LOOKUP_TABLE[];          \
                                         static const unsigned int _lenght;              \
                                      public:                                            \
                                         enum { __VA_ARGS__ };                           \
                                                                                         \
                                         NAME(int value)                                 \
                                         {                                               \
                                             bool trying = true;                         \
                                                                                         \
                                             for(int i = 0; i < _lenght ; ++i)           \
                                                trying = _ENUM_LOOKUP_TABLE[i] != value; \
                                                                                         \
                                             if(trying) throw;                           \
                                         }                                               \
                                      };                                                 \
                                                                                         \
                                      const int NAME::_ENUM_LOOKUP_TABLE[] = { __VA_ARGS__ }; \
                                      const unsigned int NAME::_lenght = sizeof(NAME::_ENUM_LOOKUP_TABLE) / sizeof(int);

GENERATE_ENUM(MyEnum,ONE,TWO,THREE,FOUR,FIVE,SIX)                           

int main()
{
    MyEnum e(33); //This throws an exception (Is not a value of the enum)

    cout << MyEnum::THREE << endl; //Scoped enums, like C++11
    cout << MyEnum::ONE   << endl;
    return 0;
}

对于此示例,CPP生成以下代码:

#include <iostream>
using namespace std;


class MyEnum 
{ 
private: 
    static const int _ENUM_LOOKUP_TABLE[]; 
    static const unsigned int _lenght; 
public: 
    enum { ONE , TWO , THREE , FOUR , FIVE , SIX }; 

    MyEnum (int value) 
    { 
        bool trying = false; 
        for(int i = 0; i < _lenght ; ++i) 
            trying = _ENUM_LOOKUP_TABLE[i] != value; 

        if(trying) throw; 
    } 
}; 
const int MyEnum ::_ENUM_LOOKUP_TABLE[] = { ONE , TWO , THREE , FOUR , FIVE , SIX };
const unsigned int MyEnum ::_lenght = sizeof( MyEnum ::_ENUM_LOOKUP_TABLE) / sizeof(int);  



int main()
{
    MyEnum e(33); //This throws an exception (Is not a value of the enum)

    cout << MyEnum::THREE << endl;
    cout << MyEnum::ONE   << endl;
    return 0;
}

我认为这是最简单的宏解决方案,并且完全涵盖了您的问题。

答案 5 :(得分:0)

您在C ++ 11习惯用法中遇到过C ++ 03问题。

带有一个参数的“构造函数调用”符号X(2)实际上是C样式转换(X) 2的简写,后者又转换为有毒的reinterpret_cast< X >( 2 )。范围枚举的卖点之一是它们不能与int进行转换。

除了类类型之外,避免使用Type( initializer )语法,并且不应该获得意外的值。 (嗯,如果海湾合作委员会对此有警告,那就太好了,但我找不到一个。)

在代码中替换“uniform initialization”X{ 2 }会导致编译器抱怨正确的错误。

答案 6 :(得分:0)

我已经考虑了很多,它需要一个Core C ++语义添加。您要查找的内容在C ++中不可用,除了标准中定义的两种类型:boolnullptr_t

实际上,标准中明确指出类型bool的对象(是的,我指的是标准中使用的对象,即标准中使用的名称)的值为true或{ {1}}。 (在未定义行为范围之外)这意味着false类型的(例如本地)变量在定义的行为领域中不能假定除booltrue之外的任何其他值,虽然它有更多价值的存储空间。 (比如简化为256)这是在语言层面上处理的,没有描述false值的存储要求,而是有关bool转换的要求。 (int必须转换为false0必须转换为true)还要求1位域必须正确转换为{{1} }}

实际上,这在C ++中还不存在。有一种方法来定义行为符合您希望的:1类型实际上意味着(并允许)许多有趣的事情。 bool类型包装齐全,拥挤到法定限制。任何值都可以转换为enum类型,前提是它适合基础类型,并且可以安全地将其转换回int。而且你没有“免费存储空间”,也没有类型没有的值。

现在,我们假设此功能存在,并且它被称为enum。您可以指定enum映射到此:

explicit enum

由于只有两个值被“采用”,并且说“我想定义一个具有bool和第三个值的类型:

explicit enum bool {
    false,
    true
};

然后,您将强烈键入这些类型的变量。您可以隐式地从bool转换为explicit enum tristate { using bool; undefined }; 。您可以明确地从bool转换为tristate。编译器可以在tristate变量上传播条件,编译器会知道bool然后是tristate,它是否已经对x != undefined && x != false变量进行了处理。它还可以确定x == true语句中不需要bool个案,每次尝试向default添加新值时都会恶意编译失败。

您必须禁止来自switch的任意转化。

这是一份撰写并提交给委员会的提案,是对explicit enum小组的讨论。