std::cout работает с uint8_t как с символом

Если я запускаю этот код:

std::cout << static_cast<uint8_t>(65);

Он выведет:

A

Что является ASCII-эквивалентом числа 65. Это потому, что uint8_t просто определяется как:

typedef unsigned char uint8_t;
  • Является ли такое поведение нормой?

  • Не лучше ли определить uint8_t, с которым гарантированно будут обращаться как с числом, а не с символом?

Я не могу понять логику, что если я хочу напечатать значение переменной uint8_t, оно будет напечатано как символ.

P.S. Я использую MSVS 2013.


person Humam Helfawi    schedule 25.08.2016    source источник
comment
Да это норм поведения. Если вы хотите использовать uint8_t как небольшое целое число без знака, вам нужно привести его перед выводом. Например, static_cast<uint32_t>(some_uint8_t_variable)   -  person Some programmer dude    schedule 25.08.2016
comment
Спасибо .. Это не дублируется точно .. Я знаю, почему такое поведение существует. Моими основными двумя вопросами были: является ли это стандартным поведением и почему нет лучшего способа представить uint8_t как несимвольный тип.   -  person Humam Helfawi    schedule 25.08.2016
comment
@HumamHelfawi Какой это может быть тип? Только char гарантированно имеет sizeof() 1.   -  person NathanOliver    schedule 25.08.2016
comment
@NathanOliver Да, это именно мой вопрос. Почему это не может быть примитивный тип, который также имеет размер 1 с другим именем.   -  person Humam Helfawi    schedule 25.08.2016
comment
@HumamHelfawi - не существует примитивного типа размером 1, который не является вариантом char. Да, C++ мог бы добавить новый тип, но это противоречило бы C, где возникли эти определения типов.   -  person Pete Becker    schedule 25.08.2016
comment
std::cout << +static_cast<uint8_t>(65); сделает то, что вы хотите.   -  person Pete Becker    schedule 25.08.2016
comment
Как сказал Пит, другого типа нет. bool и все остальное имеет размер, определенный реализацией. Единственный существующий тип с известным размером 1 — это варианты char.   -  person NathanOliver    schedule 25.08.2016
comment
@PeteBecker спасибо .. это + унарный оператор, возвращающий некоторое целое число? потому что я не мог использовать его объявление в MSVS IDE. Он сказал, что нет перегрузки для оператора +   -  person Humam Helfawi    schedule 25.08.2016
comment
+ — это унарный оператор, который во многом похож на унарный -, за исключением того, что он не инвертирует значение. Обычно его считают бессмысленным, но здесь он полезен, поскольку в качестве арифметического оператора компилятор продвигает свой аргумент к int. Итак, +x эквивалентно (int)x.   -  person Pete Becker    schedule 25.08.2016
comment
@NathanOliver Понятно. Однако, как сказал Пит, это проблема совместимости с C. Я вижу, что нет выбора, кроме char или совершенно нового типа.   -  person Humam Helfawi    schedule 25.08.2016
comment
@HumamHelfawi это будет ошибка компилятора, если +static_cast<uint8_t>(65) не удастся выполнить ваш компилятор   -  person M.M    schedule 25.08.2016
comment
@M.M Нет, это не так .. Я просто не мог видеть его реализацию или объявление (проблема IDE, а не проблема компилятора)   -  person Humam Helfawi    schedule 25.08.2016
comment
Возможный дубликат uint8_t не может быть напечатан с помощью cout   -  person underscore_d    schedule 13.04.2018
comment
FWIW, совсем не гарантируется, что вы даже сможете печатать типы uint[_fast|_least]N_t таким образом. Например, вы должны сначала выполнить приведение к unsigned long long.   -  person L. F.    schedule 22.07.2019


Ответы (3)


Является ли это поведение стандартом

Поведение стандартное в том смысле, что если uint8_t является typedef для unsigned char, то он всегда будет печатать символ, поскольку std::ostream имеет перегрузку для unsigned char и выводит содержимое переменной в виде символа.

Не должен ли быть лучший способ определить uint8_t, который гарантированно будет обрабатываться как число, а не символ?

Для этого комитет C++ должен был ввести новый фундаментальный тип. В настоящее время единственными типами, у которых sizeof() равен 1, являются char, signed char и unsigned char. Возможно, они могли бы использовать bool, но bool не обязательно должен иметь размер 1, и тогда вы все еще в той же лодке, поскольку

int main()
{
    bool foo = 42;
    std::cout << foo << '\n';
}

будет печатать 1, а не 42, так как любое значение, отличное от нуля, равно true, а true печатается как 1, но по умолчанию.

Я не говорю, что это невозможно сделать, но это большая работа для чего-то, что можно обработать с помощью приведение или функция


C++17 представляет std::byte, который определяется как enum class byte : unsigned char {};. Таким образом, это будет один байт шириной, но это не символьный тип. К сожалению, поскольку это enum class, у него есть свои ограничения. Для него были определены побитовые операторы, но для него нет встроенных потоковых операторов, поэтому вам нужно будет определить свои собственные для ввода и вывода. Это означает, что вы все еще конвертируете его, но, по крайней мере, вы не будете конфликтовать со встроенными операторами для unsigned char. Это дает вам что-то вроде

std::ostream& operator <<(std::ostream& os, std::byte b)
{
    return os << std::to_integer<unsigned int>(b);
}

std::istream& operator <<(std::istream& is, std::byte& b)
{
    unsigned int temp;
    is >> temp;
    b = std::byte{b};
    return is;
}

int main()
{
    std::byte foo{10};
    std::cout << foo;
}
person NathanOliver    schedule 25.08.2016

Публикация ответа, так как в комментариях есть некоторая дезинформация.

uint8_t может быть или не быть typedef для char или unsigned char. Также возможно, что это расширенный целочисленный тип (и, следовательно, не символьный тип).

Компиляторы могут предлагать другие целочисленные типы помимо минимального набора, требуемого стандартом (short, int, long и т. д.). Например, некоторые компиляторы предлагают 128-битный целочисленный тип.

Это также не будет «конфликтовать с C», поскольку C и C++ допускают расширенные целые типы.

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

Лично я думаю, что было бы более разумно, если бы стандарт требовал, чтобы uint8_t не был символьным типом, поскольку поведение, которое вы заметили, неинтуитивно.

person M.M    schedule 25.08.2016
comment
Да именно то, о чем я думал. Спасибо - person Humam Helfawi; 25.08.2016
comment
Если стандарт предписывает, что это не должен быть символьный тип, какой тип следует использовать? - person NathanOliver; 25.08.2016
comment
@NathanOliver 8-битный целочисленный тип, который не является символьным типом. - person M.M; 25.08.2016

Это косвенно стандартное поведение, потому что ostream имеет перегрузку для unsigned char, а unsigned char является typedef для того же типа uint8_t в вашей системе.

§27.7.3.1 [output.streams.ostream] дает:

template<class traits>
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>&, unsigned char);

Однако я не смог найти нигде в стандарте, где прямо указывалось бы, что uint8_t и unsigned char должны быть одинаковыми. Просто разумно, что они оба занимают 1 байт почти во всех реализациях.

 std::cout << std::boolalpha << std::is_same<uint8_t, unsigned char>::value << std::endl; // prints true

Чтобы получить значение для печати в виде целого числа, вам нужен тип, отличный от unsigned char (или одного из других перегруженных символов). Вероятно, достаточно простого приведения к uint16_t, потому что в стандарте для него не указана перегрузка:

uint8_t a = 65;
std::cout << static_cast<uint16_t>(a) << std::endl; // prints 65

Демо

person AndyG    schedule 25.08.2016
comment
Они не должны быть одинаковыми; uint8_t может быть расширенным целочисленным типом. - person M.M; 25.08.2016
comment
Вы не найдете ничего, что на самом деле объясняло бы, каким должен быть базовый тип uint8_t, поскольку uint8_t может даже не существовать. - person NathanOliver; 25.08.2016
comment
uint16_t может иметь ту же проблему в системе с 16-битной char. (хотя такие редкость) - person M.M; 25.08.2016
comment
@M.M: я пытался донести, что они не обязательно должны быть одинаковыми, просто они есть, и OP (и всем остальным, кто спрашивает о печати uint8_t) просто не повезло, что их реализация выбрала unsigned char в качестве определения типа для него. , и это, вероятно, не изменится в ближайшее время, потому что это разумная реализация. - person AndyG; 25.08.2016