Да, и результат такой, какой вы ожидаете.
Давайте сломаем это.
Каково значение r
в этот момент? Что ж, потеря значимости четко определена и приводит к тому, что r
принимает свое максимальное значение к моменту запуска сравнения. std::size_t
не имеет конкретных известных границ, но мы можем сделать разумные предположения о его диапазоне по сравнению с диапазоном int
:
std::size_t
— это беззнаковый целочисленный тип результата оператора sizeof. [..] std::size_t
может хранить максимальный размер теоретически возможного объекта любого типа (включая массив).
И, просто чтобы не мешать, выражение -1
является унарным -
, применяемым к литералу 1
, и имеет тип int
в любой системе:
[C++11: 2.14.2/2]:
Тип целочисленного литерала является первым из соответствующего списка в таблице 6, в котором может быть представлено его значение. [..]
(Я не буду приводить весь текст, описывающий, как применение унарного -
к int
приводит к int
, но это так.)
Более чем разумно предположить, что в большинстве систем int
не сможет содержать std::numeric_limits<std::size_t>::max()
.
Что происходит с этими операндами?
[C++11: 5.10/1]:
Операторы ==
(равно) и !=
(не равно) имеют те же семантические ограничения, преобразования и тип результата, что и операторы отношения, за исключением их более низкого приоритета и истинностного результата. [..]
[C++11: 5.9/2]:
Обычные арифметические преобразования выполняются над операндами арифметического или перечислительного типа. [..]
Давайте рассмотрим эти «обычные арифметические преобразования»:
[C++11: 5/9]:
Многие бинарные операторы, которые ожидают операнды арифметического или перечислительного типа, вызывают преобразования и возвращают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата.
Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:
- Если любой из операндов относится к типу перечисления с областью действия (7.2), преобразования не выполняются; если другой операнд не имеет того же типа, выражение имеет неправильный формат.
- Если один из операндов имеет тип
long double
, другой должен быть преобразован в long double`.
- В противном случае, если один из операндов равен
double
, другой должен быть преобразован в double
.
- В противном случае, если один из операндов равен
float
, другой должен быть преобразован в float
.
- Otherwise, the integral promotions (4.5) shall be performed on both operands.59 Then the following rules shall be applied to the promoted operands:
- If both operands have the same type, no further conversion is needed.
- В противном случае, если оба операнда имеют целые типы со знаком или оба имеют целые типы без знака, операнд с типом меньшего целочисленного ранга преобразования должен быть преобразован в тип операнда с большим рангом.
- В противном случае, если операнд, имеющий целочисленный тип без знака, имеет ранг, больший или равный рангу типа другого операнда, операнд с целочисленным типом со знаком должен быть преобразован в тип операнда с целочисленным типом без знака.
- В противном случае, если тип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целочисленным типом без знака, операнд с целочисленным типом без знака должен быть преобразован в тип операнда с целочисленным типом со знаком.
- В противном случае оба операнда должны быть преобразованы в целочисленный тип без знака, соответствующий типу операнда с целочисленным типом со знаком.
Я выделил отрывок, который вступает в силу здесь, и почему:
[C++11: 4.13/1]
: Каждый целочисленный тип имеет ранг целочисленного преобразования, определяемый следующим образом.
- [..]
- Ранг
long long int
должен быть выше ранга long int
, который должен быть выше ранга int
, который должен быть выше ранга short int
, который должен быть выше ранга signed char
.
- Ранг любого целочисленного типа без знака должен быть равен рангу соответствующего целочисленного типа со знаком.
- [..]
Все целочисленные типы, даже с фиксированной шириной, состоят из стандартных интегральных типов; следовательно, логически std::size_t
должно быть unsigned long long
, unsigned long
или unsigned int
.
Если std::size_t
равно unsigned long long
или unsigned long
, то ранг std::size_t
выше ранга unsigned int
и, следовательно, ранга int
.
Если std::size_t
равно unsigned int
, ранг std::size_t
равен рангу unsigned int
и, следовательно, также int
.
В любом случае, в соответствии с обычными арифметическими преобразованиями, знаковый операнд преобразуется в тип беззнакового операнда (и, что особенно важно, не наоборот!). Теперь, что влечет за собой это преобразование?
[C++11: 4.7/2]:
Если целевой тип беззнаковый, результирующее значение является наименьшим целым числом без знака, соответствующим исходному целому (по модулю 2n, где n — количество битов, используемых для представления беззнакового типа). [ Примечание: В представлении с дополнением до двух это преобразование является концептуальным, и в битовом шаблоне нет изменений (если есть не является усечением). —конец примечания ]
[C++11: 4.7/3]:
Если целевой тип подписан, значение не изменяется, если оно может быть представлено в целевом типе (и ширине битового поля); в противном случае значение определяется реализацией.
Это означает, что std::size_t(-1)
эквивалентно std::numeric_limits<std::size_t>::max()
; очень важно, чтобы значение n в приведенном выше предложении относилось к количеству битов, используемых для представления типа unsigned, а не исходного типа. В противном случае мы бы сделали std::size_t((unsigned int)-1)
, что совсем не одно и то же, оно может быть на много порядков меньше желаемого значения!
Действительно, теперь, когда мы знаем, что все конверсии четко определены, мы можем проверить это значение:
std::cout << (std::size_t(-1) == std::numeric_limits<size_t>::max()) << '\n';
// "1"
И, просто чтобы проиллюстрировать мою точку зрения ранее, в моей 64-битной системе:
std::cout << std::is_same<unsigned long, std::size_t>::value << '\n';
std::cout << std::is_same<unsigned long, unsigned int>::value << '\n';
std::cout << std::hex << std::showbase
<< std::size_t(-1) << ' '
<< std::size_t(static_cast<unsigned int>(-1)) << '\n';
// "1"
// "0"
// "0xffffffffffffffff 0xffffffff"
person
Lightness Races in Orbit
schedule
10.12.2014
r--
не является недостаточным переполнением, хотя неформально его можно так называть. - person Keith Thompson   schedule 10.12.2014const bool result = (r == (size_t)-1);
. Просто чтобы убедиться, что компилятор интерпретирует-1
какsize_t
, а неr
как какую-то другую переменную типа. Но ваш вариант тоже может быть правильным, просто нужно взглянуть на стандарт. - person ST3   schedule 11.12.2014