Почему счетчик стандартного алгоритма С++ возвращает разность_типа вместо размера_t?

Почему возвращаемый тип std::count является difference_type итераторов (часто ptrdiff_t) .

Поскольку count никогда не может быть отрицательным, разве size_t технически не правильный выбор? А что, если количество превышает диапазон ptrdiff_t, поскольку теоретически возможный размер массива может быть size_t?


EDIT: Пока нет подходящего ответа, почему функция возвращает ptrdiff_t. Некоторое объяснение, полученное из приведенных ниже ответов, заключается в том, что возвращаемый тип — iterator_traits<InputIterator>::difference_type, который является общим и может быть любым. До этого момента это имеет смысл. Бывают случаи, когда количество может превышать size_t. Однако по-прежнему непонятно, почему тип возвращаемого значения для стандартных итераторов — typedef ptrdiff_t iterator_traits<InputIterator>::difference_type, а не typedef size_t iterator_traits<InputIterator>::difference_type.


person Samaursa    schedule 21.09.2011    source источник
comment
Согласно моей документации, это iterator_traits<InputIterator>::difference_type.   -  person Flexo    schedule 21.09.2011
comment
@awoodland: iterator_traits<InputIterator>::difference_type - это typedef для ptrdiff_t   -  person Samaursa    schedule 21.09.2011
comment
Это зависит от типа InputIterator — это параметр шаблона.   -  person Fred Larson    schedule 21.09.2011
comment
Хотя это не обязательно. Из интереса, что возвращает Allocator::max_size() для распределителя, используемого с вашим контейнером? Я подозреваю, что это не проблема для всего, кроме возможно char, у которого может быть другая специализация.   -  person Flexo    schedule 21.09.2011
comment
@awoodland: Это не звучит как рассуждения инженера, пишущего функции STL: P. Это не проблема, как не проблема использования int для итерации через контейнер vector... до тех пор, пока у вас не будет контейнера, достаточно большого, чтобы выйти за пределы диапазона int.   -  person Samaursa    schedule 21.09.2011
comment
@Samaursa - единственный контейнер, который может это сделать, - это char. Могу поспорить, что iterator_traits::difference_type уже существовал, когда это было добавлено (оно было добавлено после другого более старого варианта count), и они не хотели принудительно обновлять каждую специализацию iterator_traits. Я не могу найти точную точку, в которой это было добавлено (хотя я ищу), но если сделать это без добавления еще одного typedef, область изменения будет очень локальной.   -  person Flexo    schedule 21.09.2011
comment
@awoodland - Кто сказал, что итераторы должны принадлежать контейнеру? Последовательность может быть на диске или частью сетевого трафика. Определяемый пользователем итератор может иметь разность_типа намного больше, чем size_t.   -  person Bo Persson    schedule 21.09.2011
comment
@awoodland: вы предполагаете архитектуру с простой схемой адресации. Вы когда-нибудь слышали об экзотических архитектурах, о которых заботится комитет по стандартизации C++?   -  person André Caron    schedule 21.09.2011
comment
@BoPersson, тогда именно так - специализация iterator_traits дает вам свободу иметь типы, которые не просто std::size_type.   -  person Flexo    schedule 21.09.2011
comment
@awoodland: Это хорошо, но почему это неправильно? В этом случае ptrdiff_t не имеет преимущества перед size_t, только недостаток.   -  person Samaursa    schedule 21.09.2011
comment
@Samaursa - это не ptrdiff_t, это iterator_traits‹InputIterator›::difference_type   -  person Flexo    schedule 21.09.2011
comment
@Samaursa Прямое использование std::size_t имеет серьезный недостаток: целочисленный тип не может меняться в зависимости от типа итератора.   -  person André Caron    schedule 21.09.2011
comment
А, теперь я понимаю. Хорошо, эта часть имеет смысл, но часть, где difference_type является определением типа ptrdiff_t, не имеет смысла. Конечно, я могу изменить это для своих собственных итераторов, но зачем выбирать ptrdiff_t для стандартных итераторов?   -  person Samaursa    schedule 21.09.2011
comment
@Samaursa, я полагаю, комитет по стандартам хотел, чтобы difference_type мог указать разницу между любыми двумя итераторами, даже если разница была отрицательной.   -  person Mark Ransom    schedule 21.09.2011


Ответы (7)


Алгоритм std::count() опирается на тип итератора для определения целочисленного типа, достаточно большого для представления любого размера диапазона. Возможная реализация контейнеров включает в себя файлы, сетевые потоки и т. д. Нет гарантии, что весь диапазон сразу поместится в адресное пространство процесса, поэтому std::size_t может быть слишком маленьким.

Единственный целочисленный тип, предлагаемый стандартом std::iterator_traits<>, — это std::iterator_traits<>::difference_type, который подходит для представления «расстояний» между двумя итераторами. Для итераторов, реализованных как (оболочки) указателей, этот тип — std::ptrdiff_t. В трейтах итераторов нет size_type или им подобных, так что другого выбора нет.

person André Caron    schedule 21.09.2011

size_t технически не является правильным выбором, так как он может быть недостаточно большим. Итераторам разрешено перебирать «что-то», что больше любого объекта в памяти — например, файл на диске. Когда они это сделают, итератор может определить тип больше size_t как свой difference_type, если он доступен.

difference_type необходимо подписать, поскольку в контекстах, отличных от std::count, он представляет смещения между итераторами в обоих направлениях. Для итераторов с произвольным доступом it + difference является совершенно разумной операцией, даже если difference отрицательно.

iterator_traits не предлагает тип без знака. Возможно, так и должно быть, но, учитывая, что это не так, iterator_traits<InputIterator>::difference_type это лучший доступный тип.

Вопрос о том, должны ли итераторы предлагать беззнаковый тип, вероятно, связан с массовым конфликтом стилей кодирования, следует ли вообще использовать беззнаковые типы для подсчета. Я не предлагаю воспроизводить этот аргумент здесь, вы можете посмотреть его. У ptrdiff_t есть недостаток, заключающийся в том, что в некоторых системах он не может представить все допустимые различия указателей и, следовательно, не может представить все ожидаемые результаты std::count.

Насколько я могу судить, даже в C++03 стандарт фактически запрещал это, может быть, случайно. 5.7/6 говорит о том, что вычитание может привести к переполнению ptrdiff_t, как это делает C. Но в таблице 32 (требования к распределителю) сказано, что X::difference_type может представлять разницу между любыми двумя указателями, а std::allocator гарантированно использует ptrdiff_t в качестве своего difference_type (20.1.5/4). С++ 11 аналогичен. Таким образом, одна часть стандарта считает, что вычитание указателя может переполнить ptrdiff_t, а другая часть стандарта говорит, что не может.

std::count предположительно был разработан в соответствии с тем же (возможно, ошибочным) предположением, что и требования распределителя, что ptrdiff_t достаточно велико, чтобы выразить размер любого объекта, и (в общем) difference_type итератора может выражать количество итерандов между любыми двумя итераторами.

person Steve Jessop    schedule 11.02.2013
comment
Должны ли итераторы определять size()? Размер возвращает size_t. - person Violet Giraffe; 10.03.2015
comment
@VioletGiraffe: это неправильно. Если C является контейнером, то C::size() возвращает C::size_type. Нет требования, чтобы size_type контейнера был size_t. Это может быть любой тип без знака, и он может быть больше size_t. Контейнеры и распределители в стандарте используют size_t, но не все контейнеры или распределители являются стандартными. - person Steve Jessop; 10.03.2015
comment
Верно, size_type. Дело в том, что size_type не имеет знака, и count не имеет смысла возвращать значение со знаком. - person Violet Giraffe; 10.03.2015
comment
В любом случае ответ - нет. У вас могут быть итераторы для вещей, отличных от коллекций, хотя общий пример состоит в том, что указатель — это итератор для массива. Что не определяет size(), но, конечно, все еще подходит для size_t ;-) Если вы предлагаете, чтобы iterator_traits определял беззнаковый тип, вам придется спорить с комитетом по стандартам, но два ответа здесь обсуждают неизбежный факт, что он не определяет один. - person Steve Jessop; 10.03.2015

Тип возвращаемого значения — typename iterator_traits<InputIterator>::difference_type, в данном случае это ptrdiff_t.

Предположительно было выбрано difference_type, потому что максимальное количество совпадающих элементов в диапазоне было бы разностью итераторов last - first.

person Mark B    schedule 21.09.2011
comment
Проблема в том, что эта разница отрицательна только в том случае, если диапазон недействителен, а использование такого недопустимого диапазона в стандартном алгоритме является неопределенным поведением. - person R. Martinho Fernandes; 21.09.2011
comment
@Р. Мартиньо Фернандес, difference_type, очевидно, может содержать полный счетчик при любых обстоятельствах, а ptrdiff_t нет — рассмотрим случай итератора над файловым объектом, когда размер указателя составляет всего 32 бита. - person Mark Ransom; 21.09.2011
comment
@Tomalak, потому что количество должно быть меньше или равно размеру диапазона. Это предполагает, что difference_type гарантированно содержит размер диапазона, который я не проверял, но я был бы очень удивлен, если бы это было не так. - person Mark Ransom; 21.09.2011
comment
@Mark: в таблице 28 n3290 говорится, что это тип, который может представлять разницу между любыми двумя указателями в модели распределения. Я полагаю, это то же самое. :) - person Lightness Races in Orbit; 21.09.2011
comment
@MarkRansom: AFAIK, difference_type не гарантированно удерживает размер диапазона из-за того, что стандарт допускает __far указатели. - person ildjarn; 21.09.2011
comment
@ildjarn, существование указателей __far автоматически не исключает гарантии, поскольку разность_типа специализируется на типе итератора. - person Mark Ransom; 21.09.2011

Первоначально std::count было:

template <class InputIterator, class EqualityComparable, class Size>
void count(InputIterator first, InputIterator last, 
           const EqualityComparable& value,
           Size& n);

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

Я подозреваю, что когда новая форма:

template <class InputIterator, class EqualityComparable>
iterator_traits<InputIterator>::difference_type
count(InputIterator first, InputIterator last, 
      const EqualityComparable& value);

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

Таким образом, использование iterator_traits вместо простого использования std::size_type означает, что каждый возможный итератор получает возможность указать, какой именно тип должен быть возвращен std::count. Это включает в себя пользовательские итераторы, которые считывают из сети или с диска, которые могут использовать что-то гораздо большее, чем ptrdiff_t или size_type и им подобные. (При необходимости это может быть своего рода "BigInt"). Это также означает, что пользователь не несет ответственности за вывод соответствующего типа для использования, что может быть сложно именно из-за возможности пользовательского итератора.

person Flexo    schedule 21.09.2011

Хотя счетчик не может быть отрицательным, тип возвращаемого значения указан как iterator_traits<InputIterator>::difference_type, а разница между двумя итераторами может быть отрицательной.

person Mark Ransom    schedule 21.09.2011
comment
Что только вызывает вопрос - почему тип возвращаемого значения std::iterator_traits<InputIterator>::difference_type, а не std::size_t? - person ildjarn; 21.09.2011
comment
Конечно, я понимаю, что разница может быть отрицательной... но это не имеет ничего общего с counting... (EDIT: не видел сообщение ildjam) - person Samaursa; 21.09.2011
comment
Это не отвечает на вопрос, так как std::count не возвращает разницу между двумя итераторами. - person Nawaz; 21.09.2011
comment
@ildjarn, difference_type, очевидно, может содержать полный счетчик при любых обстоятельствах, но size_t нет - рассмотрим случай итератора над файловым объектом, когда размер указателя составляет всего 32 бита. - person Mark Ransom; 21.09.2011
comment
@Nawaz: нет другого целочисленного типа, который можно вывести из типа итератора, кроме std::iterator_traits<>::difference_type. - person André Caron; 21.09.2011

Если бы итератор был массивом, это означало бы, что результат находится в пределах диапазона массива.

Для этого конкретного алгоритма я не могу придумать интересную причину. Тем не менее, для тех, кто использует это как компонент, это может быть интересно.

На странице сказано, что она сделает что-то эквивалентное. Таким образом, в случае массива это может сделать что-то вроде прямой разницы указателей. Это была бы довольно быстрая специализация, если бы она была применима.

person Tom Kerr    schedule 21.09.2011

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

тип, который может представлять разницу между любыми двумя указателями в модели распределения

Естественный тип для этого — ptrdiff_t.

Для size_type он говорит:

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

Естественный тип здесь — size_t.

Теперь для подсчета любых элементов в диапазоне (или массиве) нужен хотя бы тип, подходящий для указания разницы last-first. Кажется наиболее естественным выбрать именно его.

person PlasmaHH    schedule 21.09.2011
comment
Нет, это совсем не естественно. size_t может по определению также указывать разницу last-first. - person Konrad Rudolph; 21.09.2011
comment
@KonradRudolph: Конечно, size_t может содержать значение (когда оно положительное), но когда last и first являются указателями, тип определяется как тот же, что и std::ptrdiff_t, определяющий тип. - person PlasmaHH; 21.09.2011