在 C++ 中将 int 转换为 enum 的通用方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4165439/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic way to cast int to enum in C++
提问by Leonid
Is there a generic way to cast int
to enum
in C++
?
有没有一种通用的方法可以投射int
到enum
in C++
?
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
值,否则抛出一个exception
。有没有办法通用地编写它?enum type
应该支持不止一个。
Background: I have an external enumtype and no controlover the source code. I'd like to store this value in a database and retrieve it.
背景:我有一个外部枚举类型并且无法控制源代码。我想将此值存储在数据库中并检索它。
采纳答案by Steve Jessop
The obvious thing is to annotate your 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.
对于 emin 是最小枚举数而 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
实际出现在其定义中。
回答by John Dibling
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.
但这比上面更丑陋,更容易出错,并且不会按您的意愿抛出。
回答by luke
No- there's no introspection in C++, nor is there any built in "domain check" facility.
不 - 在 C++ 中没有内省,也没有任何内置的“域检查”工具。
回答by Sjoerd
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?
如果按照您的描述,这些值在数据库中,为什么不编写一个代码生成器来读取该表并创建一个包含枚举和to_enum(int)
函数的 .h 和 .cpp 文件?
Advantages:
好处:
- Easy to add a
to_string(my_enum)
function. - Little maintenance required
- Database and code are in synch
- 轻松添加
to_string(my_enum)
功能。 - 几乎不需要维护
- 数据库和代码同步
回答by Simone
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 hereto switch on values.
然后,您可以使用我在此处发布的代码来打开值。
回答by janm
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.
如果您准备将枚举值列为模板参数,您可以在 C++ 11 中使用 varadic 模板执行此操作。您可以将其视为一件好事,允许您在不同的上下文中接受有效枚举值的子集;在解析来自外部来源的代码时通常很有用。
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);
}
回答by Simone
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 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 的每个整数以查看它们是否与某个枚举的值匹配?
回答by Tom
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 替代“丑陋”版本,允许多个枚举。使用初始化列表而不是开关,更简洁的 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 );
}
}