char 枚举中的值分配不正确

Incorrect assignment of values in char enum

我正在玩枚举并尝试重现一些示例 from this 页面。初始示例按预期工作,但是我使用以下代码得到了一些有趣的结果:

#include <iostream>

enum num : char {
    zero = '0',
    one = '1',
    two = '2',
    three = '3',
    four = '4',
    five = '5',
    six = '6'
};

int main()
{
    const char two = '2';
    std::cout << two << std::endl;
    std::cout << num::two;
    return 0;
}

输出为:

2
50

我希望两个结果相同,但 num::two 似乎打印了一些其他值。此外,此值不会更改 (50),因此我假设这不是 random/garbage 值,并且正在进行某种我不理解的 char/int 解析?这里是the ideone link

我知道我可以通过像这样分配 zero = 0 来使它工作,没有单引号并且它工作。但是,我想知道幕后发生了什么,以及如何控制可以通过单引号分配打印的非个位数值。

因为两者调用了两个不同的运算符重载:

  • 第一个为std::ostreamchar调用非成员operator<<。这将打印字符。

  • 第二个示例由于整数提升而调用成员 operator<< 以获取 int,如其他答案所述。

根据 C++ 标准(4.5 积分提升)

4 A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.

因此应用整数提升并调用 int 类型对象的运算符 <<。

原因是你的 enum : charchar 不一样(这正是我们想要的 - 我们不希望枚举与其他类型相同,即使它们赋值兼容——我们希望 void func(num n)void func(char n) 不同,对吧?)。

因此,由于 enum num 不是 char,因此将使用 operator<<(int) 并打印整数值,即使基础类型是 char .不完全明智,但我确定会发生这种情况。

当你说enum num : char时,你表达了这样一个事实,即num是内部实现的char,但仍然可以自动转换为整数值,即不一定 char.

正如您引用的页面所说:

Values of unscoped enumeration type are implicitly-convertible to integral types.

请参阅 Why does a value of an enum with a fixed underlying type of char resolve to fct(int) instead of fct(char)? 以了解关于 C++ 标准措辞中有关整数提升和固定基础类型组合的问题的有趣讨论。

在任何情况下,你都可以把这整个事情想象成一个 class 带有一个私有 char 成员变量和一个 public int 转换运算符:

// very similar to your enum:
class num {
private:
    char c;

public:
    num(char c) : c(c) {}
    operator int() const {
        return c;
    }
};

num two { '2' };
std::cout << two; // prints 50

为了增加类型安全并使 std::cout 行成为编译错误,只需将 enum 变成 enum class:

enum class num : char

这又类似于上面想象的 class num,但没有转换运算符。

当您将 num 的实例提供给 std::cout 时,您是 num 的客户端,并且在逻辑上不应该认为输出格式将采用其内部 char 考虑实施。

要更好地控制输出格式,您应该像使用任何其他自定义类型一样,为 std::ostream 重载 operator<<。示例:

#include <iostream>

enum class num : char {
    zero = '0',
    one = '1',
    two = '2',
    three = '3',
    four = '4',
    five = '5',
    six = '6'
};

std::ostream& operator<<(std::ostream& os, num const& n)
{
    switch (n)
    {
        case num::zero: os << "Zero"; break;
        case num::one: os << "One"; break;
        case num::two: os << "Two"; break;
        case num::three: os << "Three"; break;
        // and so on
    }
    return os;
}

int main()
{
    std::cout << num::two; // prints "Two"
}

当然,枚举实例的特定 char 值现在已经变得毫无用处,所以您不妨完全摆脱它们:

enum class num : char {
    zero,
    one,
    two,
    three,
    four,
    five,
    six
};

这可能会让您感到奇怪,但请记住,只表示从零到六的通用数字的枚举不是一个现实的用例。

这实际上应该转到 char 重载;不幸的是 none 有问题的编译器实现了 DR 1601.

[conv.prom]/4:

A prvalue of an unscoped enumeration type whose underlying type is fixed ([dcl.enum]) can be converted to a prvalue of its underlying type.

这意味着num可以提升到char

Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.

所以num也可以升为int

相关候选人是:

template <class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
                                        char ch );
template<class charT, class Traits>
basic_ostream<charT, Traits>& basic_ostream<charT, Traits>::operator<<(int);

对于两位候选人,第一个参数是身份转换,第二个参数是晋升。 numcharnumint都有晋级。

DR1601 之前,这些都一样好,所以 template/non-template 决胜局进来了。第一个是函数模板;第二个是普通成员函数,所以第二个获胜。

DR1601 添加了一条规则:

A conversion that promotes an enumeration whose underlying type is fixed to its underlying type is better than one that promotes to the promoted underlying type, if the two are different.

这意味着 numchar 现在比 numint 更好,所以第一个重载现在是更好的匹配,应该被选择。