【问题标题】:Incorrect assignment of values in char enumchar 枚举中的值分配不正确
【发布时间】:2016-02-21 12:31:01
【问题描述】:

我在玩枚举并试图重现一些示例 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),所以我认为这不是一个随机/垃圾值,并且正在进行某种我不明白的 char/int 解析?这是the ideone link。

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

【问题讨论】:

  • @Drop 我也假设了这一点,但是为什么const char two = '2'; 会按预期工作?两者都使用单引号赋值,这使得它更加混乱。
  • 这与分配有什么关系?您的标题应该要求解释输出的原因。
  • @JDługosz 分配,因为相同的形式分配,例如 one = '1' 和 char c = '1' 产生不同的输出,并且在撰写此问题时,我忽略了运算符重载部分。如果我知道答案,我为什么会在这里发布?
  • 实际上,= 的这些用法都不是赋值。由于该问题比您最初预期的更普遍,因此您可以编辑标题。

标签: c++ c++11 enums type-conversion implicit-conversion


【解决方案1】:

这实际上应该转到char 重载;不幸的是,所讨论的编译器都没有实现DR 1601。

[conv.prom]/4:

无作用域枚举类型的纯右值,其基础类型为 fixed ([dcl.enum]) 可以转换为其底层的prvalue 类型。

这意味着num 可以提升为char。

此外,如果可以将积分提升应用于其底层 type,无作用域枚举类型的纯右值,其基础类型 是固定的,也可以转换为提升的底层证券的prvalue 输入。

所以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);

对于两个候选人,第一个参数是身份转换,第二个参数是晋升。 num 到 char 和 num 到 int 都有晋升等级。

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

DR1601 添加了一条规则:

促进其基础类型为的枚举的转换 固定到它的底层类型比一个提升到 提升的基础类型(如果两者不同)。

这意味着 num 到 char 现在比 num 到 int 更好,所以现在第一个重载是更好的匹配,应该选择。

【讨论】:

  • 这是给c++14的,是吗?
  • @bolov 该更改是针对 C++11 的 DR,因此应该具有追溯性。
  • @StoryTeller eel.is/c++draft/over.ics.rank#4.2 是新的措辞。我在 C++14 中看到它
【解决方案2】:

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

4 无作用域枚举类型的纯右值,其基础类型为 fixed (7.2) 可以转换为其基础类型的纯右值。 此外,如果可以将积分提升应用于其底层类型, 基础类型固定的无范围枚举类型的纯右值也可以转换为提升的基础的纯右值 输入。

因此应用了积分提升并调用了 int 类型对象的运算符

【讨论】:

    【解决方案3】:

    当您说enum num : char 时,您表示num 在内部以char 的形式实现但仍可以自动转换为整数值,这是不是一定是char。

    正如您引用的页面所说:

    无范围枚举类型的值可以隐式转换为 整数类型。

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

    无论如何,您都可以将整个事情想象成一个具有私有char 成员变量和公共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 实现。

    为了更好地控制输出格式,您应该改为使用任何其他自定义类型,并将operator&lt;&lt; 重载为std::ostream。示例:

    #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
    };
    

    这可能会让您感到奇怪,但请记住,仅代表从 0 到 6 的通用数字的枚举并不是一个现实的用例。

    【讨论】:

    • 很好的详细解释;但是(对于风格点)我鼓励将 operator&lt;&lt; 的实现切换为 switch 语句。编译器会为无法处理所有枚举数的开关生成警告(如果您放弃 default 的情况):swtich(n) { case num::zero: return os &lt;&lt; "Zero"; case ... } throw std::runtime_error("Unknown enumerator");。
    • @MatthieuM.: 你不能在枚举类中使用switch。
    • @MatthieuM.:我的错。会改变的。
    【解决方案4】:

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

    • 第一个为std::ostream 和char 调用非成员operator&lt;&lt;。这将打印字符。

    • 第二个示例调用成员 operator&lt;&lt; 为 int 由于整数提升,如其他答案所述。

    【讨论】:

    • 是的。在将字符与字符串组合时,您必须非常非常小心。 "Abc" + '1' 通常可以毫无问题地编译,但肯定不会做你想做的事,从而导致崩溃。
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-21
      • 1970-01-01
      • 2013-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多