这是我的源代码(作为答案),如何实现underlying_value和to_enum函数。 How to automatically convert strongly typed enum into int?
underlying_value - 没问题。 但是,to_enum - 有问题。
请参阅:
enum class E{ a = 1, b = 3, c = 5 };
auto e_a = utils::underlying_value(E::a); //OK
E t = utils::to_enum<E>( 2 ) ; // compiled, but it's incorrect. I think here must throws exception?
问:如何正确实现to_enum?
答案 0 :(得分:1)
尽管有关于这个问题的评论,但这可以在C ++ 11中完成,但是如果没有代码重复,你最终必须将enum class
声明包装在一个宏中。这可能会使我的答案不合适,具体取决于您的需求。无论哪种方式,执行检查转换需要一些机制,所以我最后会到达宏。
基本思想是使用constexpr
函数扫描数组:
#include <iostream>
#include <stdexcept>
enum class E { a = 1, b = 3, c = 5 };
constexpr E values[] = {E::a, E::b, E::c};
constexpr size_t count = sizeof(values) / sizeof(E);
constexpr E to_enum(int value, size_t index = 0)
{
return
index >= count ? throw std::runtime_error("invalid integer") :
static_cast<int>(values[index]) == value ? values[index] :
to_enum(value, index + 1);
}
constexpr E converted = to_enum(3);
// Will not compile if uncommented.
// constexpr E bad_converted = to_enum(2);
int main()
{
std::cout << static_cast<int>(converted) << std::endl;
return 0;
}
这会打印3
。如果取消注释bad_converted
的行,则该代码根本不会编译,正如它所说的那样。检查的转换可以在运行时或编译期间完成。如果to_enum
的参数是编译时常量,它将在编译期间完成。另外,正如您可能看到的,这会对values
执行线性扫描,但如果它成为非常大的枚举的性能问题,则可以用其他算法替换。
我刚刚展示的代码是一个草图,显示了基础方法。为了减少使用这种痛苦,你应该将E
的声明包装在一个自动生成values[]
数组和相关函数的宏中。我将一次展示并合理化这个宏的内容。
基本宏看起来像这样
// Declaration header
#define ENUM_CLASS(TypeName, __VA_ARGS__)
// Use
ENUM_CLASS(E, a = 1, b = 3, c = 5);
因此,在此示例中,__VA_ARGS__
将成为令牌a = 1, b = 3, c = 5
。因此,我们可以在宏中声明枚举本身,如下所示:
enum class TypeName { __VA_ARGS__ };
但是,我们不能简单地声明:
constexpr TypeName values[] = { __VA_ARGS__ };
因为它扩展到
constexpr TypeName values[] = { a = 1, b = 3, c = 5 };
没有范围(在每个值前面缺少TypeName::
),并且由于数组初始值设定项中的额外赋值运算符而无效C ++。我先解决第二个问题。你需要定义一个这样的类:
template <typename E>
class swallow_assignment {
public:
E _value;
constexpr explicit swallow_assignment(E value) : _value(value)
{
}
template <typename Any>
constexpr const swallow_assignment& operator =(Any other) const
{
return *this;
}
constexpr operator E() const
{
return _value;
}
};
现在,您可以撰写(swallow_assignment<E>)E::a = 1
。将要发生的是,在编译时,E::a
将转换为可分配值(swallow_assignment<E>)E::a
,其具有与E::a
相同的内部表示。该值将忽略1
的分配,然后将转换回E::a
。
剩下的是为每个声明的常量添加前缀,以便我们得到
constexpr TypeName values[] =
{(swallow_assignment<E>)E::a = 1,
(swallow_assignment<E>)E::b = 3,
(swallow_assignment<E>)E::c = 5})
现在将成为有效的初始值设定项。这可以使用映射宏来完成。我不会在这里详细介绍,因为这是一个完全独立的主题,但可以在https://github.com/aantron/better-enums/blob/e28177b11a9e3d7152c5216d84fdf8939aff0eba/enum_preprocessor_map.h找到这样的宏。提升可能也会更好。无论你使用什么宏,我都会认为它的签名是PP_MAP(prefix, __VA_ARGS__)
。然后,整个枚举的最终宏定义的草图变为:
#define ENUM_CLASS(TypeName, __VA_ARGS__) \
enum class TypeName { __VA_ARGS__ }; \
constexpr TypeName values[] = \
{ PP_MAP((swallow_assignment<TypeName>)TypeName::, \
__VA_ARGS__) }; \
constexpr size_t count = sizeof(values) / sizeof(TypeName);
您可能希望将这些定义填充到traits类型的特化中,以便您可以将此宏与多个enum class
一起使用(否则名为values
的数组将发生冲突)。但是,如果使values
成为特征类的静态成员,则可能必须使用弱符号来避免链接问题。
这些最后一点是作为练习留下的,因为这个答案已经太长了:)我有一个库可以完成上述所有操作,虽然它包含enum
而不是提供特征的特征enum class
。但是,有一个未发布的分支,其中包含enum class
/特征的组合。您可以在此处查看图书馆:http://aantron.github.io/better-enums。库的::_from_integral()
方法对应于您问题中的to_enum
函数,它同时执行运行时和编译时转换。