Должен ли читать отрицание в unsigned с ошибкой через std :: cin (gcc, clang не согласен)?

Например,

#include <iostream>

int main() {
  unsigned n{};
  std::cin >> n;
  std::cout << n << ' ' << (bool)std::cin << std::endl;
}

При вводе -1, clang 6.0.0 выдает 0 0 а gcc 7.2.0 выходы 4294967295 1. Мне интересно, кто прав. Или, может быть, оба правильны для стандарта, не указывают это? В противном случае я подразумеваю (bool)std::cin будет оцениваться как false. clang 6.0.0 также -0 ввода -0.

Ответ 1

Я думаю, что оба они ошибаются в С++ 17 1 и что ожидаемый результат должен быть:

4294967295 0

Это, однако, трудно понять из стандарта...

В стандарте написано - [facet.num.get.virtuals # 3.3]:

Последовательность символов, накопленных на этапе 2 (поле), преобразуется в числовое значение по правилам одной из функций, объявленных в заголовке <cstdlib>:

  • Для знакового целочисленного значения используется функция strtoll.

  • Для целочисленного значения без знака функция strtoull.

  • Для значения с плавающей точкой функция strtold.

Поэтому мы возвращаемся к std::strtoull, который должен возвращать 2ULLONG_MAX и не устанавливать errno в этом случае (что и делают оба компилятора).

Но в том же блоке (акцент мой):

Числовое значение, которое необходимо сохранить, может быть одним из следующих:

  • ноль, если функция преобразования не преобразует все поле.

  • самое положительное (или отрицательное) представимое значение, если поле, которое должно быть преобразовано в целочисленный тип со знаком, представляет собой слишком большое положительное (или отрицательное) значение, которое должно быть представлено в val.

  • самое положительное представимое значение, если поле, которое должно быть преобразовано в целочисленный тип без знака, представляет значение, которое не может быть представлено в val.

  • преобразованное значение, в противном случае.

Полученное числовое значение сохраняется в val. Если функция преобразования не преобразует все поле, или если поле представляет собой значение вне диапазона представимых значений, ios_base​::​failbit присвоенный err.

Обратите внимание, что все эти разговоры о "поле, которое нужно преобразовать", а не фактическое значение, возвращаемое std::strtoull. Поле здесь на самом деле является расширенной последовательностью символов '-', '1'.

Поскольку поле представляет значение (-1), которое не может быть представлено unsigned, возвращаемое значение должно быть UINT_MAX а failbit должен быть установлен на std::cin.


1clang был фактически прав до С++ 17, потому что третья пуля в приведенной выше цитате была:

- самое отрицательное представимое значение или ноль для целого числа без знака, если поле представляет слишком большое значение, которое должно быть представлено в val.ios_base::failbit присваивается err.

2 std::strtoull возвращает ULLONG_MAX потому что (спасибо @NathanOliver) - C/7.22.1.4.5:

Если субъектная последовательность имеет ожидаемую форму и значение базы равно нулю, последовательность символов, начинающихся с первой цифры, интерпретируется как целочисленная константа в соответствии с правилами 6.4.4.1.[...] Если последовательность объектов начинается с знака минус, значение, полученное в результате преобразования, отменяется (в возвращаемом типе).

Ответ 2

Предложенная семантика вашей команды std::cin >> n описана здесь (поскольку, по-видимому, для этой операции std::num_get::get()). В этой функции были некоторые изменения семантики, в частности, по выбору того, стоит ли размещать 0 или нет, в С++ 11, а затем снова на С++ 17.

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

Ответ 3

cppreference довольно хорошо устраняет эти несоответствия:

Результат преобразования строки с отрицательным числом в беззнаковое было задано целое число, чтобы получить ноль до , хотя некоторые реализации следовали протоколу std::strtoull, который отрицает в типе цели, давая ULLONG_MAX для "-1", и, таким образом, получим Наибольшее значение целевого типа вместо. Начиная с строго следующее std::strtoull - правильное поведение.

Это сводится к:

  • ULLONG_MAX (4294967295) верный шаг вперед, так как (оба компилятора делают это правильно сейчас)
  • Ранее это должен был быть 0 со строгим чтением стандарта
  • Некоторые реализации (в частности, ) и clang также (несмотря на то, что видит OP) вместо этого следовали протоколу std::strtoull (который теперь считается правильным поведением)

Что касается точки сбоя: она не должна быть установлена в любом из случаев, также в случае OP она выглядит так, как будто она не работает по не связанной причине (которая также может объяснить, почему существует ноль) начать с).