3

C++11 提供了一个改进enumenum struct. 但这仍然会受到肯定——直到你习惯它——最令人惊讶的劣质旧陷阱的影响enum:类型变量的值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 structtype的对象E,您都必须抓住它“不是Es 之一”的情况。

如何定义一种通用类型,以代替enum struct(不一定是直接替代)具有实例化类的对象不能 采用“枚举”对象以外的值的属性?

我的意思是,如果对象将抛出而不是假设任何未区分的值(即捕捉到它将成为“不是其中之一”的情况),我的意思是不能满足。E

我在引号中说“枚举”,因为似乎不可避免(不求助于宏)这些值将被一系列整数类型模板参数“枚举”,并且不能像X::one.

如果这是不可避免的,那很好,只要“枚举”值成为可以通过类型的枚举索引静态检索的常量。(然后客户端代码很容易将有意义的符号与索引或索引值相关联,并封装这种方便的映射 - 例如在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
4

7 回答 7

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;
}
于 2013-06-16T09:39:15.007 回答
0

您在 C++11 习语中遇到了 C++03 问题。

带有一个参数的“构造函数调用”表示法X(2)实际上是 C 风格 cast 的简写(X) 2,而后者又转换为toxic reinterpret_cast< X >( 2 )。范围枚举的卖点之一是它们不会转换为int.

避免使用Type( initializer )除类类型之外的语法,并且您不应该得到意外的值。(嗯,如果 GCC 对此有警告,那就太好了,但我找不到。)

在代码中替换“统一初始化”X{ 2 }会导致编译器抱怨正确的错误。

于 2013-06-17T10:21:47.473 回答
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);
}

我不喜欢这种方法,主要是因为错误消息没有帮助。然而,它保证在编译时失败。

这是你想要的?

于 2013-06-16T14:01:50.553 回答
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
}
于 2013-06-16T15:54:22.407 回答
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;
}

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

于 2013-06-16T16:13:53.797 回答
0

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

实际上,标准中明确指出,类型的对象(是的,我的意思是对象,这是标准中使用的名称)的bool值是trueor false。(在未定义行为的领域之外)这意味着类型的(例如局部)变量bool在已定义的行为领域中不能假定除trueor之外的任何其他值false,尽管它可以存储更多值。(让我们说 256 来简化)这是在语言级别通过不描述bool值的存储要求来处理的,而是描述与int. ( falsemust convert to 0and truemust convert to 1) 还有一个要求是:1位域必须正确转换为bool.

事实上,这在 C++ 中还不存在。有一种方法来定义enum按照您希望的方式运行的类型实际上意味着(并允许)许多有趣的事情。enum类型被打包,满员,拥挤到法定限制。只要适合基础类型,任何值都可以转换为enum类型,并且您可以安全地将其转换回 int。而且您没有“免费存储”,没有不被类型占用的值。

现在,让我们假设这个功能存在,并且它被称为explicit enum. 您可以指定bool映射到此:

explicit enum bool {
    false,
    true
};

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

explicit enum tristate {
    using bool;
    undefined
};

然后,您将对这些类型的变量进行强类型化。您可以隐式转换 frombooltristate. 您可以显式转换 fromtristatebool. 编译器可以在变量上传播条件tristate,并且编译器会知道 if x != undefined && x != falsethen x == true,它已经对bool变量执行的方式。它还可以确定您的语句中不需要defaultcase,switch每次您尝试将新值添加到您的explicit enum.

您必须禁止从int.

这将是一个写并提交给委员会的提案,一个对std-proposals小组进行的讨论。

于 2014-02-04T10:19:12.643 回答
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;
};
于 2013-06-16T09:47:17.117 回答