Почему всегда указывать итераторы в std :: for_each? - PullRequest
2 голосов
/ 11 апреля 2011

Насколько я могу судить, идиома для перебора коллекций STL выглядит примерно так:

int a[] = { 1,2,3 };
std::vector<int> v(a, a+3);

std::for_each(a.begin(), a.end(), some_function);

Указание первого и последнего итераторов полезно, если я хочу работать только в определенном диапазонеколлекцию, или сделать что-то более креативное, но в большинстве случаев я подозреваю, что мы действительно хотим работать со всей коллекцией.Итак, мне интересно, почему люди утруждают себя указанием итераторов в этой ситуации (так как они всегда будут одинаковыми), а не просто используют вспомогательную функцию по следующим направлениям:

namespace easy
{
  template <class T, class F>
  F for_each(T& collection, F function)
  {
    std::for_each(collection.begin(), collection.end(), function);
    return function;
  }
}

(OfКонечно, возможно, что - идиоматический способ делать вещи, и я никогда не замечал! Хотя я новичок в C ++.)

Ответы [ 4 ]

5 голосов
/ 11 апреля 2011

Я полностью гагага за STL, абсолютно.Но я не могу вспомнить, чтобы когда-либо использовалось for_each.

Идиома:

for ( container::iterator it = coll.begin(); it != coll.end(); ++ it )

C ++ 11 вводит сахар, чтобы уменьшить это до

for ( auto elem : coll )

Это похоже на вашу удобную функцию, но использует бесплатные (не являющиеся членами) функции std::begin и std::end, которые обеспечивают совместимость с объектами, которые не являются стандартными контейнерами.

Кроме того, при поиске (я не знаю)с этим поиграться, так как его еще нет в GCC), похоже, он ограничивает доступ программиста к элементам диапазона, а не к итераторам.


Что касается использования контейнера для обращения кво всей полноте, гораздо предпочтительнее поддерживать гибкость, позволяющую использовать поддиапазоны.Альтернативное решение - ввести идиому для пары итераторов { begin, end }.Были некоторые споры, и я ожидал, что C ++ 11 включит такую ​​функцию, что

begin( make_pair( begin_, end_ ) ) // == begin_,
end( make_pair( begin_, end_ ) ) // == end_,
for ( auto elem : make_pair( begin_, end_ ) ) // iterates over [ begin_, end )

, но после прочтения стандарта кажется, что pair не имеет этой функциональности.

Вы можетеоднако создайте свой собственный pair, чтобы получить гибкий диапазон на основе for:

template< typename iter >
struct range_type {
    iter first, last;

    // use friends because Standard specifies these should be found by ADL:
    friend iter begin( range_type const &r ) { return r.first; }
    friend iter end( range_type const &r ) { return r.last; }
};

template< typename iter >
range_type< iter > range( iter first, iter last )
    { return range_type< iter >{ first, last }; }

// usage:
for ( auto elem : range( begin_, end_ ) ) // iterates over [ begin_, end )
2 голосов
/ 03 июня 2011

Указание начального и конечного итераторов вместо просто коллекции действительно утомительно и подвержено ошибкам (например, ваш пример кода ошибочно пытался вызвать .begin() и .end() для a вместо v).Вот почему Boost Range был изобретен.С его помощью вы можете написать код, такой как:

int a[] = { 1,2,3 };
boost::for_each(a, some_function);

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

[Предположение] Причина, по которой STL использует итераторы вместо диапазонов, заключается в том, что он был задуман с точки зрения построения алгоритмов, а затем поиска минимальных требований для этих алгоритмов и их выражения в терминах этих требований.Алгоритмы требуют итераторов, чтобы выполнять свою работу, хотя естественная вещь, на которой используются алгоритмы, это на самом деле диапазоны значений.Как было упомянуто в другом ответе, STL испытывал серьезную нехватку времени, и поэтому эти вопросы никогда не решались.К счастью, у нас, современных людей, есть Boost.Range, и поэтому мы можем использовать алгоритмы, основанные на диапазоне.

1 голос
/ 11 апреля 2011

Причина в том, что разработчики STL пришли с теоретической стороны.В Comp.Sci диапазон является более фундаментальной концепцией, чем контейнер.На практике контейнеры встречаются гораздо чаще.STL был добавлен под серьезным давлением времени еще в 1996-1998 годах, и он не подвергался агрессивному рефакторингу.

Вы видите аналогичную проблему с третьим аргументом std::for_each.В теории лямбда существует.В C ++ 98 они этого не сделали.Поэтому вам нужно было либо определить функтор вне строки, использовать подробный синтаксис со связывателями и композиторами, либо использовать указатель на функцию без сохранения состояния.

Лично я думаю, что все алгоритмы STL должны были принимать диапазон (один объект), а не пару итераторов.Это тривиально, чтобы обернуть последний в диапазоне.Теперь вы видите, что ostream_iterators должны определять довольно произвольный конечный объект.

1 голос
/ 11 апреля 2011

Нет ничего плохого в том, что вы предлагаете, хотя я бы сказал, что Boost.ForEach примерно настолько же близок к "идиоматическому", что и вещи в современной вселенной C ++.

Преимущество этого подхода, который при использовании выглядит примерно так:

BOOST_FOREACH(value_type i, collection) {
    function(i);
}

заключается в том, что вы также можете встроить свои операции и не быть строго привязанными к явному функциональному отображению.

...