Например,
#include <iostream>
int main() {
unsigned n{};
std::cin >> n;
std::cout << n << ' ' << (bool)std::cin << std::endl;
}
Когда вводится -1, лязг 6.0.0 выводит 0 0, а gcc 7.2.0 выводит 4294967295 1. Интересно, кто прав. А может оба правильные, ибо в стандарте этого не указано? Под неудачей я подразумеваю, что (bool)std::cin будет оценен как ложный. clang 6.0.0 также не может вводить -0.
Начиная с Clang 9.0.0 и GCC 9.2.0, оба компилятора, использующие libstdC++ или libC++ в случае Clang, соглашаются с результатом программы выше, независимо от используемой версии C++ (> = C++ 11), и распечатать
4294967295 1
т.е. они устанавливают значение ULLONG_MAX и не устанавливают бит отказа в потоке.
Пытались ответить на этот вопрос ... это кроличья нора. Как минимум, вы могли бы добавить, какой стандарт C++ вы компилируете, существует так много изменений «до», «после» и т. д., Что незнание этого сделает почти невозможным дать окончательный ответ.
@ArndtJonasson Я бы предположил, что 'fail' означает, что failbit входного потока был установлен - и поэтому второй выход будет 0, а не 1.
@RichardCritten Добавлен тег C++ 17.
Дополнительные ссылки: ошибка libC++ приводит к изменению поведения и Проблема LWG приводит к изменению C++ 17. Ни из одного из них мне не ясно, следует ли устанавливать failbit.





Предполагаемая семантика вашей команды std::cin >> n описывается здесь (поскольку, по-видимому, для этой операции вызывается std::num_get::get()). В этой функции произошли некоторые семантические изменения, в частности w.r.t. выбор, помещать ли 0 или нет, в C++ 11, а затем снова в C++ 17.
Я не совсем уверен, но считаю, что эти различия могут объяснить различное поведение, которое вы наблюдаете.
Я думаю, что оба варианта ошибочны в C++ 171 и что ожидаемый результат должен быть:
4294967295 0
Хотя возвращаемое значение является правильным для последних версий обоих компиляторов, я думаю, что следует установить ios_base::failbit, но я также думаю, что существует путаница в отношении понятия поле для преобразования в стандарте, который может учитывать текущее поведение.
В стандарте написано - [facet.num.get.virtuals # 3.3]:
The sequence of chars accumulated in stage 2 (the field) is converted to a numeric value by the rules of one of the functions declared in the header
<cstdlib>:
For a signed integer value, the function
strtoll.For an unsigned integer value, the function
strtoull.For a floating-point value, the function
strtold.
Поэтому мы возвращаемся к std::strtoull, который должен возвращать 2ULLONG_MAX , а не устанавливать errno в этом случае (что и делают оба компилятора).
Но в том же блоке (акцент мой):
The numeric value to be stored can be one of:
zero, if the conversion function does not convert the entire field.
the most positive (or negative) representable value, if the field to be converted to a signed integer type represents a value too large positive (or negative) to be represented in
val.the most positive representable value, if the field to be converted to an unsigned integer type represents a value that cannot be represented in
val.the converted value, otherwise.
The resultant numeric value is stored in
val. If the conversion function does not convert the entire field, or if the field represents a value outside the range of representable values,ios_base::failbitis assigned toerr.
Обратите внимание, что все это говорит о "поле для преобразования", а не о фактическом значении, возвращаемом std::strtoull. Поле здесь представляет собой расширенную последовательность символов '-', '1'.
Поскольку поле представляет значение (-1), которое не может быть представлено unsigned, возвращаемое значение должно быть UINT_MAX, а бит отказа должен быть установлен на std::cin.
1clang was actually right prior to C++17 because the third bullet in the above quote was:
- the most negative representable value or zero for an unsigned integer type, if the field represents a value too large negative to be represented in
val.ios_base::failbitis assigned toerr.
2std::strtoull returns ULLONG_MAX because (thanks @NathanOliver) — C/7.22.1.4.5:
If the subject sequence has the expected form and the value of base is zero, the sequence of characters starting with the first digit is interpreted as an integer constant according to the rules of 6.4.4.1. [...] If the subject sequence begins with a minus sign, the value resulting from the conversion is negated (in the return type).
Я полагаю, что вы ищете последовательность символов, начинающаяся с первой цифры, интерпретируется как целочисленная константа в соответствии с правилами п. 6.4.4.1. с Если субъектная последовательность начинается со знака минус, значение, полученное в результате преобразования, инвертируется (в возвращаемом типе) из 7.22.1.4.5 стандарта C. Думаю, на этом можно было бы сделать этот ответ «стандартно полным» :)
@NathanOliver Я добавил это, но на самом деле я переписываю ответ, потому что я нашел некоторые другие доказательства в стандарте - я удаляю его, пока редактирую. В любом случае спасибо за цитату!
@NathanOliver Я обновил ответ, я был бы рад узнать вашу точку зрения на него.
Я не уверен, где вы взяли вторую цитату, но она не соответствует тому, что у меня есть для C++ 17: timsong-cpp.github.io/cppwp/facet.num.get.virtuals#3.3. Соответственно, это должно быть самое положительное значение.
Ах, взглянул на мой черновик C++ 11, и это язык там. Похоже, где-то по ходу дела произошли изменения.
@NathanOliver N4296 (последний черновик C++ 14, если я не ошибаюсь), я обновлю вашу цитату, поскольку этот вопрос помечен как C++ 17. Я не думаю, что это меняет мой вывод.
Ага, я все еще думал, что он должен хранить максимум в n, но iirc, если установлен failbit, значение, переданное в cin, устанавливается на 0. +1
@NathanOliver Установлен битовый бит, не следует ли не трогать значение вместо того, чтобы установить его на 0?
@NathanOliver Я думаю, что вы на самом деле правы и что n должен быть равен UINT_MAX, но бит отказа должен быть установлен, поэтому оба компилятора будут ошибаться?
@Lingxi и holt, по крайней мере, это говорит, что он должен быть установлен в 0.
@NathanOliver Я не нашел ссылки в это, и я просто вспомнил, почему я никогда не отвечал на вопросы, связанные с потоками на C++;)
Без шуток. Потоки - это PITA.
@NathanOliver На самом деле, я думаю, что cppreference (и связанный ответ) ссылаются на первую пулю выше «ноль, если функция преобразования не преобразует все поле»., которая происходит, если ввод не содержит действительного числа.
В этом есть большой смысл. Если это так, то оба неверны, так как на выходе должно быть 4294967295 0: coliru.stacked-crooked.com/a/ea3475f2633adcd9. Жаль, что я снова не могу +1
В конце вы говорите, что бит ошибки должен быть установлен, но в начале вы говорите, что этого не следует ...
@ Холт Да ладно. Я записал то, что вы сказали.
Обратите внимание, что Пример strtoul в cppreference без проблем преобразует -40 в беззнаковый, так почему же -1 может быть ошибкой?
@BoPersson Об этом упоминается в моем ответе - поведение strtoull ожидается, но возвращаемое значение std::num_get::get не является напрямую преобразованным значением, возвращаемым strtoull, что я обсуждаю в конце своего ответа.
@BoPersson В частности, цитата «наиболее положительное представимое значение, если поле быть преобразованным для целочисленного типа без знака представляет значение, которое не может быть представлено в val». подразумевает (с моей точки зрения) проверку фактической последовательности символов, а не только возвращаемого значения strtoull.
Другое правдоподобное прочтение состоит в том, что значение, представленное «полем для преобразования», является значением, определенным по правилам strto*, что делает GCC правильным.
@ T.C. Я согласен, что это не на 100% однозначно, но зачем в этом случае использовать «поле для преобразования» вместо «преобразованное значение»? Более того, даже в этом случае gcc, вероятно, будет ошибаться, поскольку преобразованная функция - это strtoull, которая вернет ULLONG_MAX, который, вероятно, не может быть представлен беззнаковым int.
@Holt Это возникло в связи с другим вопросом, и я заметил, что последние версии GCC и Clang согласны с этим, но не согласны с вашим ответом. Не могли бы вы вернуться к нему?
@uneven_mark Можете указать мне на другой вопрос? Я не заметил изменений в стандарте, которые могли бы повлиять на это, поэтому в настоящее время я буду настаивать на этом (даже после того, как перечитал это в 10-й раз), если не будет доказано обратное или если нет каких-либо «официальных» аргументов от gcc и лязг.
@Holt Эта проблема на самом деле не обсуждалась, но в этот вопрос код работал бы так, как предполагалось, если бы бит отказа был установлен для отрицательных входов, хотя OP этого вопроса, похоже, неправильно понял unsigned в целом. Я был удивлен поведением, когда сам проверил его и искал связанный вопрос, и в итоге оказался здесь.
Поскольку это кажется очень простым вопросом при выполнении операций ввода-вывода, я подумал, что стоит попросить разъяснений. Может быть, кто-то другой предоставит рассуждения с точки зрения стандартных библиотек для поведения, особенно libC++, который, похоже, изменился с момента первоначальной публикации этого вопроса.
@Holt Здесь - это комментарий в отчете об ошибке, который вызвал изменение в поведении libC++, обсуждая, следует ли устанавливать бит отказа. Я не знаю, добавит ли это что-нибудь новое в эту ветку.
@uneven_mark Я думаю, это текущий стандарт. Если вы прочитаете стандарт для функций C strto*, вы заметите, насколько сложно их понять (просто проверьте цитату здесь). С моей точки зрения, и gcc, и clang теперь следуют спецификации функций C для возвращаемого значения (см. github.com/llvm-mirror/libcxx/commit/… для clang). [...]
@uneven_mark [...] Но насколько я понимаю, при проверке, нужно ли устанавливать err (в num_get, см., например, cplusplus.github.io/LWG/lwg-defects.html#1169), есть упоминание о поле для преобразования, которое быть заставляет меня думать, что это лишнее Проверки должны выполняться num_get, независимо от состояния errno или значения, возвращаемого strto*.
Речь идет о различиях между реализациями библиотек libC++ и libstdC++ - и не столько о различиях между компиляторами (лязгать, gcc).
cppreference довольно хорошо устраняет эти несоответствия:
The result of converting a negative number string into an unsigned integer was specified to produce zero until c++17, although some implementations followed the protocol of
std::strtoullwhich negates in the target type, givingULLONG_MAXfor "-1", and so produce the largest value of the target type instead. As of c++17, strictly followingstd::strtoullis the correct behavior.
Это сводится к:
ULLONG_MAX (4294967295) верен в будущем, поскольку C++ 17 (оба компилятора делают это правильно сейчас)0 со строгим чтением стандарта (libC++)std::strtoull вместо этого (что теперь считается правильным поведением)Набор битов отказа и почему он был установлен, может быть более интересным вопросом (по крайней мере, с точки зрения языковед). В libC++ (лязгать) версии 7 теперь он делает то же самое, что и libstdC++ - это, кажется, предполагает, что он был выбран таким же, как и в дальнейшем (хотя это противоречит букве стандарта, что он должен быть равен нулю перед C++ 17), но пока мне не удалось найти журнал изменений или документацию по этому изменению.
Интересный блок текста гласит (при условии, что версия до C++ 17):
If the conversion function results in a negative value too large to fit in the type of v, the most negative representable value is stored in v, or zero for unsigned integer types.
В соответствии с этим указано значение 0. Кроме того, нигде не указано, что это должно привести к установке биты отказа.
Верхний ответ говорит то же самое о наборе значений. Однако он дополнительно утверждает, что бит отказа должен быть установлен в обоих случаях, что не то, что делают текущие компиляторы. На странице cppreference явно не упоминается, следует ли его устанавливать. Это должно означать, что его не следует устанавливать ни в том, ни в другом случае? Тогда старое поведение Clang тоже было бы неправильным. Я думаю, это то, что нужно подробно объяснить.
@uneven_mark я обновил свой ответ - оказалось, что clang сделал то же самое, что и gcc
Это проблема библиотеки, поэтому на самом деле вместо gcc vs clang мы должны говорить о libstdC++ vs libC++. Godbolt по умолчанию использует libstdC++ для обоих. Вам нужно указать -stdlib=libc++, тогда вы увидите результаты OP. Я думаю, что необходимо более подробное объяснение того, почему не следует устанавливать failbit, учитывая, что это вопрос языкового юриста, и ответ, получивший большое количество голосов, противоречит как C++ 17, так и более ранним версиям.
@uneven_mark я почти уверен, что это ошибка в libC++ (вам не кажется?) - язык-юрист или нет
Не знаю, так ли это. Как вы можете видеть в верхнем ответе, было обсуждение того, что на самом деле означает стандарт, поскольку в нем написано «Если функция преобразования не преобразует все поле или поле представляет значение за пределами диапазона представимых значений, ios_base :: failbit присваивается err.». Стандартные библиотеки, похоже, теперь согласны с тем, что это не означает, что в этой ситуации должен быть установлен отказоустойчивый бит, но снова главный ответ утверждает иначе. Я сам не знаю, какой правильный ответ.
Возможно, позже я найду соответствующий патч для libC++. Он может содержать как минимум причину изменения разработчика libC++ (исправление ошибки?)
@uneven_mark где мы их находим? Я мог сказать, что они «исправили ошибку» с 6000 до 7000, проведя тестирование, проведенное мной.
Я бы начал с поиска в их bugzilla закрытой ошибки здесь: bugs.llvm.org Если бы я ничего не нашел, я бы попытался выяснить соответствующий код в библиотеке и git-bisect для изменения. Мы надеемся, что сообщение о фиксации будет полезным. Однако это может занять много времени.
Я связал ошибку libC++, которую нашел в комментариях к вопросу.
Что здесь означает «провал»? Вы не можете получить -1, это точно.