这是std :: underlying_type的错误吗?

时间:2014-01-10 06:10:44

标签: c++11 g++

我想我可能遇到过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上

1 个答案:

答案 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是-1UINT_MAX,两者都在[0,3]范围之外,因此static_cast的结果未指定。

由于未指定转换超出范围值的结果,您最好在基础类型的域中执行比较,这正是修复的效果。