Подтвердить что ты не робот

Неправильное назначение значений в char перечислении

Я играл с перечислениями и пытался воспроизвести некоторые примеры с этой страницы. Первоначальные примеры работали по назначению, однако я получил несколько интересных результатов со следующим кодом:

#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, который я не понимаю? Вот ссылка ideone.

Я знаю, что я могу заставить его работать, назначив таким образом zero = 0, без одинарных кавычек, и он работает. Тем не менее, я хочу знать, что происходит за кулисами, и как я могу контролировать то, что не единственное значение цифр, которое я могу распечатать с помощью одинарных кавычек.

4b9b3361

Ответ 1

Теперь это действительно перейдет к перегрузке char; К сожалению, ни один из компиляторов не выпускает DR 1601.

[conv.prom]/4:

Значение неперечисленного типа перечисления, базовый тип которого fixed ([dcl.enum]) может быть преобразован в prvalue его базового тип.

Это означает, что num можно повысить до char.

Более того, если интегральное продвижение может быть применено к его основополагающим type, prvalue неперечисленного типа перечисления, базовый тип которого фиксируется, также может быть преобразовано в 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, поэтому первая перегрузка теперь лучше соответствует и должна быть выбрана.

Ответ 2

В соответствии со стандартом С++ (4.5 Интегральные акции)

4 Значение класса неперечисленного перечисления, базовый тип которого фиксированное (7.2) может быть преобразовано в prvalue своего базового типа. Кроме того, если интегральное продвижение может быть применено к его базовому типу, prvalue неперечисленного типа перечисления, базовый тип которого фиксирован, также может быть преобразован в prvalue продвинутого базового типа Тип.

Таким образом, применяется интегральное продвижение, и оператор < для объектов типа int.

Ответ 3

Когда вы говорите enum num : char, вы выражаете тот факт, что num внутренне реализуется в терминах char, но все равно может быть автоматически преобразован в целочисленное значение, что необязательно char.

В качестве страницы, которую вы цитируете, говорится:

Значения неперечисленного типа перечисления неявно-конвертируются в интегральные типы.

См. Почему значение перечисления с фиксированным базовым типом char разрешено для fct (int) вместо fct (char)? для интересное обсуждение проблем стандартной формулировки С++ в отношении сочетания целостного продвижения и фиксированных базовых типов.

В любом случае вы можете представить все это как класс с частной переменной члена 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<< для 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 теперь стали бесполезными, поэтому вы можете полностью избавиться от них:

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

Это может показаться вам странным, но имейте в виду, что перечисление, которое не представляет ничего, кроме общих чисел от нуля до шести, не является реалистичным вариантом использования.

Ответ 4

Поскольку два вызова двух разных перегрузок операторов:

  • первый вызывает нечлен operator<< для std::ostream и char. Это печатает символ.

  • Второй пример вызывает член operator<< для int из-за целых рекламных акций, как объясняется другими ответами.

Ответ 5

Причина в том, что ваш enum : char не совпадает с char (это именно то, что мы хотим - мы не хотим, чтобы перечисление было таким же, как и другие типы, даже если они совместимы с назначением - мы хотим void func(num n) быть отличным от void func(char n), справа?).

Итак, поскольку enum num не является char, будет использоваться operator<<(int) и печатает целочисленное значение, даже если базовый тип char. Не совсем разумно, но я уверен, что это происходит.