我想我可能遇到过c ++ 11模板std :: underlying_type的错误。
我使用traits类来定义我们系统中的枚举范围。 然后我能够提供通用的is_valid函数。
我最近在启用-Wextra时扩展了该功能,因为我得到了 关于永远真实比较的很多警告。
当枚举为无符号类型且其第一个值为0时,会生成警告。
轻松解决了这个问题。但是第二天,使用该功能的模块中的一些单元测试开始了 失败。
如果未指定枚举的基础类型,它仍会选择正确的实现,但不知何故会返回错误的结果。
以下是最小示例(http://ideone.com/PwFz15):
#include <type_traits>
#include <iostream>
using namespace std;
enum Colour
{
RED = 0,
GREEN,
BLUE
};
enum NoProblems : int
{
A,
B,
C
};
enum AlsoOk : unsigned
{
D,
E,
F
};
template <typename Enum> struct enum_traits;
template <> struct enum_traits<Colour>
{
typedef Colour type;
static constexpr type FIRST = RED;
static constexpr type LAST = BLUE;
};
template <> struct enum_traits<NoProblems>
{
typedef NoProblems type;
static constexpr type FIRST = A;
static constexpr type LAST = C;
};
template <> struct enum_traits<AlsoOk>
{
typedef AlsoOk type;
static constexpr type FIRST = D;
static constexpr type LAST = F;
};
#if 0
// This implementation gives you warnings about an always true comparison
// ONLY IF you define the underlying type of your enum, such as Colour.
template <typename Enum>
inline constexpr bool is_valid(Enum e)
{
return e >= enum_traits<Enum>::FIRST && e <= enum_traits<Enum>::LAST;
}
#endif
// So you define the is_valid function like so, to prevent the warnings:
template <typename Enum, typename enable_if<is_unsigned<typename underlying_type<Enum>::type>::value && enum_traits<Enum>::FIRST == 0, int>::type = 0>
inline constexpr bool is_valid(Enum e)
{
return e <= enum_traits<Enum>::LAST;
}
template <typename Enum, typename enable_if<is_signed<typename underlying_type<Enum>::type>::value || enum_traits<Enum>::FIRST != 0, int>::type = 0>
inline constexpr bool is_valid(Enum e)
{
return e >= enum_traits<Enum>::FIRST && e <= enum_traits<Enum>::LAST;
}
int main()
{
Colour c = static_cast<Colour>(RED - 1);
cout << is_valid(c) << endl;
NoProblems np = static_cast<NoProblems>(A - 1);
cout << is_valid(np) << endl;
AlsoOk ao = static_cast<AlsoOk>(D - 1);
cout << is_valid(ao) << endl;
return 0;
}
给出了输出:
1
0
0
显然,第一次调用is_valid的输出应为0 / false。不知何故,enum同时签名和签名?
我是否错过了标准库中关于我使用的模板的一些关键文档?
可以通过执行这样的比较来修复:
return static_cast<typename std::underlying_type<Enum>::type>(e) <= enum_traits<Enum>::LAST;
但似乎没有必要这样做。
我在gcc 4.8.1,gcc 4.7.3和clang 3.2.1上试过这个,全都在x86-64上
答案 0 :(得分:2)
C ++ 11 5.2.9 [expr.static.cast] / 10:
可以将整数或枚举类型的值显式转换为枚举类型。价值是 如果原始值在枚举值(7.2)的范围内,则不变。否则,结果 值未指定(可能不在该范围内)。
“枚举值的范围”在7.2 / 7中定义:
对于其基础类型是固定的枚举,枚举的值是的值 基础类型。否则,对于枚举,其中emin是最小的枚举数,而emax是 最大值,枚举值是bmin到bmax范围内的值,定义如下:设K. 对于二进制补码表示为1,对于一个补码或符号幅度表示为0。 bmax是大于或等于max(| emin | - K,| emax |)且等于2M - 1的最小值,其中 M是非负整数。如果emin是非负的,则bmin为零,否则为 - (bmax + K)。的大小 如果bmin是,则足够大以容纳枚举类型的所有值的最小位字段是max(M,1) 否则为0和M + 1。可以定义具有未由其任何值定义的值的枚举 统计员。如果枚举器列表为空,则枚举的值就像枚举有一个 值为0的单个枚举器。
对于Colour
,枚举值的范围(假设二进制补码)为[0,3]。 RED
- 1是-1
或UINT_MAX
,两者都在[0,3]范围之外,因此static_cast
的结果未指定。
由于未指定转换超出范围值的结果,您最好在基础类型的域中执行比较,这正是修复的效果。