在C ++中将int强制转换为枚举的通用方法


81

有没有投一个通用的方法int,以enumC++

如果int落在的范围内,enum则应返回一个enum值,否则抛出exception。有没有办法通用地编写它?不止一个enum type应予以支持。

背景:我有一个外部枚举类型,无法控制源代码。我想将此值存储在数据库中并检索它。


enum e{x = 10000};在这种情况下是否9999落在的范围内enum
Armen Tsirunyan

不,9999不会跌倒。
Leonid

9
好问题。至于任何“为什么”?这将要出现,让我只说“反序列化”-对我来说似乎足够了。我也很高兴听到C ++ 0x编译器的答案enum class
科斯2010年

9
“ Range”在这里是错误的词,也许是“ domain”?
君士坦丁2010年

如果值超出范围,则boost :: numeric_cast <>会引发正或负溢出异常。但是不确定它是否也适合枚举类型。你可以试试看。
yasouser

Answers:


37

显而易见的事情是注释您的枚举:

// generic code
#include <algorithm>

template <typename T>
struct enum_traits {};

template<typename T, size_t N>
T *endof(T (&ra)[N]) {
    return ra + N;
}

template<typename T, typename ValType>
T check(ValType v) {
    typedef enum_traits<T> traits;
    const T *first = traits::enumerators;
    const T *last = endof(traits::enumerators);
    if (traits::sorted) { // probably premature optimization
        if (std::binary_search(first, last, v)) return T(v);
    } else if (std::find(first, last, v) != last) {
        return T(v);
    }
    throw "exception";
}

// "enhanced" definition of enum
enum e {
    x = 1,
    y = 4,
    z = 10,
};

template<>
struct enum_traits<e> {
    static const e enumerators[];
    static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};

// usage
int main() {
    e good = check<e>(1);
    e bad = check<e>(2);
}

您需要使用来使数组保持最新e,如果您不是该数组的作者,这将是一个麻烦。e。正如Sjoerd所说,它可以使用任何不错的构建系统来自动化。

无论如何,您都可以达到7.2 / 6:

对于emin是最小的枚举数且emax是最大的枚举,该枚举的值是bmin到bmax范围内的基础类型的值,其中bmin和bmax分别是最小值和最小值的最大值可以存储emin和emax的位字段。可以定义一个其枚举数未定义的枚举。

因此,如果您不是的作者e,则可能会或可能不会保证有效的值e实际出现在其定义中。


22

丑陋。

enum MyEnum { one = 1, two = 2 };

MyEnum to_enum(int n)
{
  switch( n )
  {
    case 1 :  return one;
    case 2 : return two;
  }
  throw something();
}

现在是真正的问题。你为什么需要这个?该代码很丑陋,不容易编写(*?),不容易维护,也不容易合并到您的代码中。它告诉您的代码错误。为什么要打架?

编辑:

另外,假设枚举是C ++中的整数类型:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

但这比上面的丑陋得多,更容易出错,并且不会如您所愿地抛出。


这仅支持一种类型,即MyEnum。
西蒙妮(Simone)2010年

2
@莱昂尼德:据我所知,这不能一概而论。在某种程度上,您想出的任何解决方案throw(将对无效类型进行(或做一些特殊的事情))都必须像我已经发布的那样进行切换。
John Dibling 2010年

2
为什么这样-1'ed?这是正确的答案。仅仅因为这不是某些人所希望的答案,并不意味着它是错误的。
John Dibling 2010年

12
Astatic_cast<MyEnum>也将正常工作,并且应优先于reinterpret_cast<MyEnum>
Sjoerd 2010年

1
这种方法效果很好,如果使用工具生成函数,效果会更好。
尼克

3

如您所描述的,如果值在数据库中,为什么不编写一个代码生成器来读取此表并创建带有枚举和to_enum(int)函数的.h和.cpp文件?

好处:

  • 容易添加 to_string(my_enum)功能。
  • 几乎不需要维护
  • 数据库和代码同步

无法控制枚举类型的源代码。我同意可以生成实现转换的功能。但是,该设施不会知道对外部枚举类型所做的任何更改/扩展(除非每次在编译时执行)。
列昂尼德(Leonid)2010年

@Leonid然后读取该枚举标头并to_enum(int)基于该标头生成函数。
Sjoerd 2010年

@Leonid每个严肃的项目管理系统,甚至make,都可以比较两个文件的日期,以查看是否必须重新运行生成器。
Sjoerd 2010年

我想我现在将使用一个更简单的生成器解决方案。但是,谢谢你的主意。
列昂尼德(Leonid)2010年

我们在工作场所使用此方案,一种工具从模板生成.hpp代码,该模板很小。
尼克

3

不,C ++中没有内省,也没有内置的“域检查”工具。


2

您如何看待这个?

#include <iostream>
#include <stdexcept>
#include <set>
#include <string>

using namespace std;

template<typename T>
class Enum
{
public:
    static void insert(int value)
    {
        _set.insert(value);
    }

    static T buildFrom(int value)
    {
        if (_set.find(value) != _set.end()) {
            T retval;
            retval.assign(value);
            return retval;
        }
        throw std::runtime_error("unexpected value");
    }

    operator int() const { return _value; }

private:
    void assign(int value)
    {
        _value = value;
    }

    int _value;
    static std::set<int> _set;
};

template<typename T> std::set<int> Enum<T>::_set;

class Apples: public Enum<Apples> {};

class Oranges: public Enum<Oranges> {};

class Proxy
{
public:
    Proxy(int value): _value(value) {}

    template<typename T>
    operator T()
    {
        T theEnum;
        return theEnum.buildFrom(_value);
    }

    int _value;
};

Proxy convert(int value)
{
    return Proxy(value);
}

int main()
{    
    Apples::insert(4);
    Apples::insert(8);

    Apples a = convert(4); // works
    std::cout << a << std::endl; // prints 4

    try {
        Apples b = convert(9); // throws    
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
    try {
        Oranges b = convert(4); // also throws  
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
}

然后,您可以使用我在此处发布的代码来启用值。


您仍然需要在Apples::insert(4)某处添加内容,因此与切换相比没有任何优势。
Sjoerd 2010年

1
他说值来自数据库,这就是为什么我添加了“插入”方法。
西蒙妮

1

您不应该希望您所描述的东西存在,我担心您的代码设计中会出现问题。

另外,您假设枚举值在一个范围内,但并非总是这样:

enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };

这不在范围内:即使有可能,您是否应该检查从0到2 ^ n的每个整数以查看它们是否与某个枚举的值匹配?


您将如何从数据库中检索枚举值?这些整数在编译时是已知的,那么为什么不能基于模板进行泛型转换呢?
Leonid

2
@Leonid:因为在某种程度上,您需要像我说的那样进行切换。
John Dibling 2010年

2
@Leonid模板不是解决您能想到的每个问题的灵丹妙药。
Sjoerd 2010年

约翰是对的。您需要比枚举更复杂的类型来执行所需的操作,我认为使用类层次结构是可行的。
西蒙妮

我发布了一个使用类层次结构的解决方案,请检查一下。
西蒙妮

1

如果您准备将枚举值作为模板参数列出,则可以在C ++ 11中使用可变模板进行此操作。您可以将此视为一件好事,允许您在不同的上下文中接受有效枚举值的子集;在从外部来源解析代码时通常很有用。

也许不是您想要的那样通用,但是检查代码本身是通用的,您只需要指定一组值即可。这种方法处理间隙,任意值等。

template<typename EnumType, EnumType... Values> class EnumCheck;

template<typename EnumType> class EnumCheck<EnumType>
{
public:
    template<typename IntType>
    static bool constexpr is_value(IntType) { return false; }
};

template<typename EnumType, EnumType V, EnumType... Next>
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
{
    using super = EnumCheck<EnumType, Next...>;

public:
    template<typename IntType>
    static bool constexpr is_value(IntType v)
    {
        return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
    }

    EnumType convert(IntType v)
    {
        if (!is_value(v)) throw std::runtime_error("Enum value out of range");
        return static_cast<EnumType>(v);
};

enum class Test {
    A = 1,
    C = 3,
    E = 5
};

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;

void check_value(int v)
{
    if (TestCheck::is_value(v))
        printf("%d is OK\n", v);
    else
        printf("%d is not OK\n", v);
}

int main()
{
    for (int i = 0; i < 10; ++i)
        check_value(i);
}

虽然此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。-评分
泰斯

1
@Tas这是指向其他SO答案的链接-它没有外部链接所具有的相同问题。无论如何都进行了更新。
2016年

0

C ++ 0x替代“丑陋”版本,允许多个枚举。使用初始化器列表而不是开关,这是更干净的IMO。不幸的是,这不能解决对枚举值进行硬编码的需要。

#include <cassert>  // assert

namespace  // unnamed namespace
{
    enum class e1 { value_1 = 1, value_2 = 2 };
    enum class e2 { value_3 = 3, value_4 = 4 };

    template <typename T>
    int valid_enum( const int val, const T& vec )
    {
        for ( const auto item : vec )
            if ( static_cast<int>( item ) == val ) return val;

        throw std::exception( "invalid enum value!" );  // throw something useful here
    }   // valid_enum
}   // ns

int main()
{
    // generate list of valid values
    const auto e1_valid_values = { e1::value_1, e1::value_2 };
    const auto e2_valid_values = { e2::value_3, e2::value_4 };

    auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
    assert( result1 == e1::value_1 );

    auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
    assert( result2 == e2::value_3 );

    // test throw on invalid value
    try
    {
        auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
        assert( false );
    }
    catch ( ... )
    {
        assert( true );
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.