Ограничить алгоритмы STL до N элементов - PullRequest
8 голосов
/ 11 ноября 2010

(вдохновлено комментарием от nakiya)

Многие алгоритмы STL принимают диапазон в качестве пары итераторов. Например, for_each(begin, end, &foo);. Очевидно, что если distance(begin, end) >= N, а begin является итератором с произвольным доступом, то for_each(begin, begin+N, &foo); применяет foo только к первым N элементам.

Теперь существует ли чистая, общая альтернатива, если какое-либо из этих двух условий не выполняется?

Ответы [ 3 ]

9 голосов
/ 11 ноября 2010

Не существует универсального полного решения без изменения типа итератора.

Доказательство: предположим, что типом итератора является только InputIterator, поэтому begin фактически ссылается (например, на поток), а end - это итератор маркера специального случая, который будет сравниваться с «реальным» Итератор, как только настоящий итератор прочитал EOF.

Тогда любое использование begin, чтобы попытаться определить новое значение end для передачи алгоритму, будет "потреблять" исходное значение begin, поскольку именно так работают InputIterators.

Что вы могли бы сделать, так это написать класс-обертку итератора, чтобы итератор считал, сколько раз он был увеличен, и сравнивает его с «конечным» итератором после его увеличения N раз. N может быть параметром шаблона или параметром конструктора для одного или другого итератора.

Как то так. Я проверил это компилируется и работает для меня. Еще предстоит сделать - я сейчас занимаюсь только одной из двух ваших ситуаций, а не «итератором с произвольным доступом». Я не обращаюсь и к другому, «расстояние

#include <iterator>

template <typename It>
class FiniteIterator : public std::iterator<
  typename std::iterator_traits<It>::iterator_category,
  typename std::iterator_traits<It>::value_type> {
    typedef typename std::iterator_traits<It>::difference_type diff_type;
    typedef typename std::iterator_traits<It>::value_type val_type;
    It it;
    diff_type count;
  public:
    FiniteIterator(It it) : it(it), count(0) {}
    FiniteIterator(diff_type count, It it = It()) : it(it), count(count) {}
    FiniteIterator &operator++() {
        ++it;
        ++count;
        return *this;
    }
    FiniteIterator &operator--() {
        --it;
        --count;
        return *this;
    }
    val_type &operator*() const {
        return *it;
    }
    It operator->() const {
        return it;
    }
    bool operator==(const FiniteIterator &rhs) const {
        return count == rhs.count;
    }
    bool operator!=(const FiniteIterator &rhs) const {
        return !(*this == rhs);
    }
    FiniteIterator operator++(int) {
        FiniteIterator cp = *this;
        ++*this;
        return cp;
    }
    FiniteIterator operator--(int) {
        FiniteIterator cp = *this;
        --*this;
        return cp;
    }
};

Обратите внимание, что второй конструктор использует только итератор, потому что базовый тип может не быть конструируемым по умолчанию (если это только InputIterator). В случае, когда вызывающий объект создает «конечный» итератор, он не использует его, поскольку он не будет действителен после увеличения другой копии.

Если основным типом итератора является RandomAccess, то эта оболочка не нужна / не нужна. Поэтому я предоставляю вспомогательную шаблонную функцию, которая делает вывод типа таким же образом, как back_inserter делает для back_insert_iterator. Однако в случае, когда его тип параметра является итератором категории произвольного доступа, помощник не должен возвращать FiniteIterator<T>, а просто T:

template <typename Iterator, typename Category>
struct finite_traits2 {
    typedef FiniteIterator<Iterator> ret_type;
    static ret_type plus(Iterator it, typename std::iterator_traits<Iterator>::difference_type d) {
        return ret_type(d, it);
    }
};

template <typename Iterator>
struct finite_traits2<Iterator, std::random_access_iterator_tag> {
    typedef Iterator ret_type;
    static ret_type plus(Iterator it, typename std::iterator_traits<Iterator>::difference_type d) {
        return it + d;
    }
};

template <typename Iterator>
struct finite_traits {
    typedef typename std::iterator_traits<Iterator>::iterator_category itcat;
    typedef typename finite_traits2<Iterator, itcat>::ret_type ret_type;
    static ret_type plus(Iterator it, typename std::iterator_traits<Iterator>::difference_type d) {
        return finite_traits2<Iterator, itcat>::plus(it, d);
    }
};

template <typename Iterator, typename Distance>
typename finite_traits<Iterator>::ret_type finite_iterator(Iterator it, Distance d) {
    return finite_traits<Iterator>::plus(it, d);
}

template <typename Iterator>
typename finite_traits<Iterator>::ret_type finite_iterator(Iterator it) {
    return finite_traits<Iterator>::plus(it, 0);
}

Пример использования (и минимальный тест):

#include <iostream>
#include <typeinfo>
#include <list>

struct MyIterator : std::iterator<std::bidirectional_iterator_tag, int> {
    difference_type count;
};

int main() {
    std::cout << typeid(MyIterator::iterator_category).name() << "\n";
    std::cout << typeid(FiniteIterator<MyIterator>::iterator_category).name() << "\n";
    std::cout << typeid(MyIterator::difference_type).name() << "\n";
    std::cout << typeid(FiniteIterator<MyIterator>::difference_type).name() << "\n";

    int a[] = {1, 2, 3, 4, 5};
    std::copy(finite_iterator(a), finite_iterator(a,4), std::ostream_iterator<int>(std::cout, " "));
    std::cout << "\n";
    std::list<int> al(finite_iterator(a), finite_iterator(a,4));
    std::cout << al.size() << "\n";
    std::copy(finite_iterator(al.begin()), finite_iterator(al.begin(),3), std::ostream_iterator<int>(std::cout, " "));
    std::cout << "\n";
}

Внимание: finite_iterator(x, 1) == finite_iterator(++x, 0) равно ложно , даже для прямого итератора или лучше. Конечные итераторы сравнимы, только если они созданы из одной и той же начальной точки.

Кроме того, это все еще не завершено. Например, std::reverse не работает, потому что для доступа к реферранду finite_iterator(x, 1) «указывает» на x.

В настоящее время работает следующее:

std::list<int>::iterator e = al.begin();
std::advance(e,3);
std::reverse(finite_iterator(al.begin()), finite_iterator(e,3));

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

5 голосов
/ 11 ноября 2010

Уже есть fill_n и generate_n, foreach_n нет (или for_n, вероятно, было бы более подходящим), но написать его достаточно просто.

template< typename FwdIter, typename Op, typename SizeType >
void for_n( FwdIter begin, SizeType n, Op op )
{
   while( n-- )
   {
      op(*begin);
      ++begin;
   }
}

Вы можете сделать op (* begin ++), но, хотя он меньше печатает, он может генерировать больше кода для копирования итератора. size_type является числовым, поэтому постинкремент не менее эффективен, и в данном случае он полезен.

0 голосов
/ 11 ноября 2010

Я полагаю, что вы могли бы создать тип итератора-обертки, похожий на boost::counting_iterator, который содержал бы и инкремент, и базовый итератор, и сравнивал бы его как "конечный" итератор, как только приращение превышает максимальное значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...