在c++中将int转换为enum的通用方式

时间:2022-03-24 16:23:24

Is there a generic way to cast int to enum in C++?

在c++中,是否有一种通用的方法将int转换为enum ?

If int falls in range of an enum it should return an enum value, otherwise throw an exception. Is there a way to write it generically? More than one enum type should be supported.

如果int在枚举范围内,它应该返回enum值,否则抛出异常。有没有一种通用的写法?应该支持多个enum类型。

Background: I have an external enum type and no control over the source code. I'd like to store this value in a database and retrieve it.

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

9 个解决方案

#1


37  

The obvious thing is to annotate your enum:

最明显的事情是注释你的enum:

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

You need the array to be kept up to date with e, which is a nuisance if you're not the author of e. As Sjoerd says, it can probably be automated with any decent build system.

如果您不是e的作者,那么您需要保持与e的日期保持一致,这是一个麻烦,正如Sjoerd所说,它很可能是自动化的,有任何良好的构建系统。

In any case, you're up against 7.2/6:

无论如何,你将面对7.2/6:

For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values of the underlying type in the range bmin to bmax, where bmin and bmax are, respectively, the smallest and largest values of the smallest bit-field that can store emin and emax. It is possible to define an enumeration that has values not defined by any of its enumerators.

枚举,艾敏是最小的枚举器和emax是最大的,枚举的值基本类型的值的范围bmin bmax,bmin和bmax,分别为最小的和最大的最小值可以存储emin和emax位域。可以定义一个枚举,它的任何枚举数都没有定义值。

So if you aren't the author of e, you may or may not have a guarantee that valid values of e actually appear in its definition.

如果你不是e的作者,你可能有也可能没有保证e的有效值会出现在它的定义中。

#2


18  

Ugly.

丑。

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

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

Now for the real question. Why do you need this? The code is ugly, not easy to write (*?) and not easy to maintain, and not easy to incorporate in to your code. The code it telling you that it's wrong. Why fight it?

现在是真正的问题。你为什么需要这个?代码很难看,不容易编写(*?),不容易维护,也不容易合并到代码中。代码告诉你它是错的。为什么战斗?

EDIT:

编辑:

Alternatively, given that enums are integral types in C++:

或者,考虑到枚举是c++中的整数类型:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

but this is even uglier that above, much more prone to errors, and it won't throw as you desire.

但这比上面的更糟糕,更容易出错,而且不会像你想的那样抛出。

#3


3  

If, as you describe, the values are in a database, why not write a code generator that reads this table and creates a .h and .cpp file with both the enum and a to_enum(int) function?

如您所述,如果值在数据库中,为什么不编写一个代码生成器来读取该表并创建一个.h和.cpp文件,其中包含enum和to_enum(int)函数?

Advantages:

优点:

  • Easy to add a to_string(my_enum) function.
  • 很容易添加to_string(my_enum)函数。
  • Little maintenance required
  • 几乎不需要维护
  • Database and code are in synch
  • 数据库和代码是同步的

#4


2  

No- there's no introspection in C++, nor is there any built in "domain check" facility.

没有——在c++中没有内省,也没有在“域检查”工具中构建。

#5


2  

What do you think about this one?

你觉得这个怎么样?

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

You could then use code I posted here to switch on values.

然后您可以使用我在这里发布的代码来切换值。

#6


1  

You should not want something like what you describe to exist, I fear there are problems in your code design.

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

Also, you assume that enums come in a range, but that's not always the case:

另外,你假设enum在一定范围内,但并非总是如此:

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

This is not in a range: even if it was possible, are you supposed to check every integer from 0 to 2^n to see if they match some enum's value?

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

#7


1  

If you are prepared to list your enum values as template parameters you can do this in C++ 11 with varadic templates. You can look at this as a good thing, allowing you to accept subsets of the valid enum values in different contexts; often useful when parsing codes from external sources.

如果您准备将enum值作为模板参数列出,那么您可以在c++ 11中使用可变模板来实现这一点。您可以将其视为一件好事,允许您在不同的上下文中接受有效enum值的子集;通常在解析来自外部源的代码时很有用。

Perhaps not quite as generic as you'd like, but the checking code itself is generalised, you just need to specify the set of values. This approach handles gaps, arbitrary values, etc.

也许不像您希望的那样通用,但是检查代码本身是通用的,您只需要指定值集。此方法处理空白、任意值等。

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

#8


0  

C++0x alternative to the "ugly" version, allows for multiple enums. Uses initializer lists rather than switches, a bit cleaner IMO. Unfortunately, this doesn't work around the need to hard-code the enum values.

c++ 0x替代“丑陋”版本,允许多个枚举。使用初始化列表而不是开关,更简洁一点。不幸的是,这并不能解决需要硬编码enum值的问题。

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

#9


-4  

Try something like this:

试试这样:

enum EType
{
  type1,
  type2
};

unsigned int number = 3;
EType e = static_cast<EType>(number);
if(static_cast<unsigned int>(e) != number)
  throw std::exception();

#1


37  

The obvious thing is to annotate your enum:

最明显的事情是注释你的enum:

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

You need the array to be kept up to date with e, which is a nuisance if you're not the author of e. As Sjoerd says, it can probably be automated with any decent build system.

如果您不是e的作者,那么您需要保持与e的日期保持一致,这是一个麻烦,正如Sjoerd所说,它很可能是自动化的,有任何良好的构建系统。

In any case, you're up against 7.2/6:

无论如何,你将面对7.2/6:

For an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values of the underlying type in the range bmin to bmax, where bmin and bmax are, respectively, the smallest and largest values of the smallest bit-field that can store emin and emax. It is possible to define an enumeration that has values not defined by any of its enumerators.

枚举,艾敏是最小的枚举器和emax是最大的,枚举的值基本类型的值的范围bmin bmax,bmin和bmax,分别为最小的和最大的最小值可以存储emin和emax位域。可以定义一个枚举,它的任何枚举数都没有定义值。

So if you aren't the author of e, you may or may not have a guarantee that valid values of e actually appear in its definition.

如果你不是e的作者,你可能有也可能没有保证e的有效值会出现在它的定义中。

#2


18  

Ugly.

丑。

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

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

Now for the real question. Why do you need this? The code is ugly, not easy to write (*?) and not easy to maintain, and not easy to incorporate in to your code. The code it telling you that it's wrong. Why fight it?

现在是真正的问题。你为什么需要这个?代码很难看,不容易编写(*?),不容易维护,也不容易合并到代码中。代码告诉你它是错的。为什么战斗?

EDIT:

编辑:

Alternatively, given that enums are integral types in C++:

或者,考虑到枚举是c++中的整数类型:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

but this is even uglier that above, much more prone to errors, and it won't throw as you desire.

但这比上面的更糟糕,更容易出错,而且不会像你想的那样抛出。

#3


3  

If, as you describe, the values are in a database, why not write a code generator that reads this table and creates a .h and .cpp file with both the enum and a to_enum(int) function?

如您所述,如果值在数据库中,为什么不编写一个代码生成器来读取该表并创建一个.h和.cpp文件,其中包含enum和to_enum(int)函数?

Advantages:

优点:

  • Easy to add a to_string(my_enum) function.
  • 很容易添加to_string(my_enum)函数。
  • Little maintenance required
  • 几乎不需要维护
  • Database and code are in synch
  • 数据库和代码是同步的

#4


2  

No- there's no introspection in C++, nor is there any built in "domain check" facility.

没有——在c++中没有内省,也没有在“域检查”工具中构建。

#5


2  

What do you think about this one?

你觉得这个怎么样?

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

You could then use code I posted here to switch on values.

然后您可以使用我在这里发布的代码来切换值。

#6


1  

You should not want something like what you describe to exist, I fear there are problems in your code design.

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

Also, you assume that enums come in a range, but that's not always the case:

另外,你假设enum在一定范围内,但并非总是如此:

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

This is not in a range: even if it was possible, are you supposed to check every integer from 0 to 2^n to see if they match some enum's value?

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

#7


1  

If you are prepared to list your enum values as template parameters you can do this in C++ 11 with varadic templates. You can look at this as a good thing, allowing you to accept subsets of the valid enum values in different contexts; often useful when parsing codes from external sources.

如果您准备将enum值作为模板参数列出,那么您可以在c++ 11中使用可变模板来实现这一点。您可以将其视为一件好事,允许您在不同的上下文中接受有效enum值的子集;通常在解析来自外部源的代码时很有用。

Perhaps not quite as generic as you'd like, but the checking code itself is generalised, you just need to specify the set of values. This approach handles gaps, arbitrary values, etc.

也许不像您希望的那样通用,但是检查代码本身是通用的,您只需要指定值集。此方法处理空白、任意值等。

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

#8


0  

C++0x alternative to the "ugly" version, allows for multiple enums. Uses initializer lists rather than switches, a bit cleaner IMO. Unfortunately, this doesn't work around the need to hard-code the enum values.

c++ 0x替代“丑陋”版本,允许多个枚举。使用初始化列表而不是开关,更简洁一点。不幸的是,这并不能解决需要硬编码enum值的问题。

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

#9


-4  

Try something like this:

试试这样:

enum EType
{
  type1,
  type2
};

unsigned int number = 3;
EType e = static_cast<EType>(number);
if(static_cast<unsigned int>(e) != number)
  throw std::exception();