Есть много полезных функций <algorithm>
, но все они работают с «последовательностями» - парами итераторов. Например, если у меня есть контейнер и мне нравится работать std::accumulate
на нем, мне нужно написать:
std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);
Когда все, что я собираюсь сделать, это:
int sum = std::accumulate(myContainer, 0);
Что немного более читабельно и понятно в моих глазах.
Теперь я вижу, что могут быть случаи, когда вы хотите работать только с частями контейнера, поэтому определенно полезно иметь возможность пропускания диапазонов. Но, по крайней мере, по моему опыту, это редкий особый случай. Я обычно хочу работать с целыми контейнерами.
Это легко написать функцию - обертку , которая принимает контейнер и звонки begin()
и end()
на ней, но такие функции удобства не входят в стандартную библиотеку.
Я хотел бы знать причину этого выбора дизайна STL.
источник
boost::accumulate
Ответы:
В вашем случае это может быть редкий особый случай , но на самом деле весь контейнер - это особый случай, а произвольный диапазон - это общий случай.
Вы уже заметили, что можете реализовать весь контейнерный случай, используя текущий интерфейс, но вы не можете сделать обратное.
Таким образом, у создателя библиотеки был выбор между реализацией двух интерфейсов заранее или только реализацией одного, который все еще охватывает все случаи.
Правда, тем более что бесплатные функции
std::begin
иstd::end
сейчас включены.Итак, допустим, библиотека обеспечивает перегрузку удобства:
теперь он также должен обеспечить эквивалентную перегрузку, принимая функтор сравнения, и нам нужно предоставить эквиваленты для любого другого алгоритма.
Но мы по крайней мере рассмотрели каждый случай, когда мы хотим работать с полным контейнером, верно? Ну, не совсем. Рассмотреть возможность
Если мы хотим работать с контейнерами в обратном направлении , нам нужен другой метод (или пара методов) для каждого существующего алгоритма.
Таким образом, основанный на диапазоне подход является более общим в простом смысле:
Конечно, есть еще одна веская причина, заключающаяся в том, что для стандартизации STL было уже очень много работы, и накачка его удобными обертками до того, как он стал широко использоваться, не будет большим использованием ограниченного времени для комитетов. Если вы заинтересованы, вы можете найти технический отчет Stepanov & Lee здесь
Как уже упоминалось в комментариях, Boost.Range предоставляет новый подход, не требуя изменений в стандарте.
источник
f(c.begin(), c.end(), ...)
и, возможно, просто наиболее часто используемой перегрузкой (как вы это определяете), чтобы избежать удвоения количества перегрузок. Кроме того, адаптеры итераторов полностью ортогональны (как вы заметили, они отлично работают в Python, чьи итераторы работают совершенно по-другому и не обладают большей мощностью, о которой вы говорите).std::sort(std::range(start, stop))
.#define MAKE_RANGE(container) (container).begin(), (container).end()
</ jk>Оказывается, есть статья Херба Саттера на эту самую тему. По сути, проблема заключается в неоднозначности перегрузки. Учитывая следующее:
И добавив следующее:
Будет трудно отличить
4
и1
правильно.Концепции, предложенные, но в конечном счете не включенные в C ++ 0x, решили бы это, и также возможно обойти это, используя
enable_if
. Для некоторых алгоритмов это не проблема. Но они решили против этого.Теперь, после прочтения всех комментариев и ответов здесь, я думаю, что
range
объекты будут лучшим решением. Я думаю, что я посмотрюBoost.Range
.источник
typename Iter
кажется слишком утонченным для строгого языка. Я бы предпочел напримерtemplate<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1
иtemplate<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4
т. Д. ( Что, возможно, решило бы проблему неоднозначности)T[]::iterator
доступных. Кроме того, правильный итератор не обязан быть вложенным типом какой-либо коллекции, его достаточно определитьstd::iterator_traits
.В основном, унаследованное решение. Концепция итератора смоделирована на указателях, но контейнеры не смоделированы на массивах. Кроме того, поскольку массивы трудно передать (в общем случае требуется параметр типа не тип для длины), довольно часто функция имеет только доступные указатели.
Но да, в ретроспективе решение неверно. Мы были бы лучше с объектом диапазона построимого от любого
begin/end
илиbegin/length
; теперь у нас есть несколько_n
суффиксных алгоритмов.источник
Добавление их не даст вам никакой силы (вы уже можете сделать весь контейнер, позвонив
.begin()
и.end()
себе), и это добавит еще одну вещь в библиотеку, которая должна быть правильно указана, добавлена в библиотеки поставщиками, протестирована, поддерживается, и т. д.Короче говоря, это, вероятно, не так, потому что не стоит беспокоиться о том, чтобы поддерживать набор дополнительных шаблонов только для того, чтобы уберечь пользователей всего контейнера от ввода одного дополнительного параметра вызова функции.
источник
std::getline
, и все же, это в библиотеке. Можно даже сказать, что расширенные структуры управления не дают мне власти, поскольку я могу делать все, используя толькоif
иgoto
. Да, несправедливое сравнение, я знаю;) Я думаю, что могу как-то понять бремя спецификации / реализации / сопровождения, но мы говорим здесь только о крошечной обертке, так что ..К настоящему моменту http://en.wikipedia.org/wiki/C++11#Range-based_for_loop - хорошая альтернатива
std::for_each
. Обратите внимание, нет явных итераторов:(Вдохновлено https://stackoverflow.com/a/694534/2097284 .)
источник
<algorithm>
, а не все действительные алгоритмы, которые нужныbegin
иend
итераторы - но выгода не может быть преувеличена! Когда я впервые попробовал C ++ 03 в 2009 году, я избегал итераторов из-за шаблона циклических циклов, и, к счастью или нет, мои проекты в то время позволяли это. Перезапуск на C ++ 11 в 2014 году, это было невероятное обновление, язык C ++ всегда должен был быть, и теперь я не могу жить без негоauto &it: them
:)