将 STL 算法限制为 N 个元素

发布于 2024-10-02 00:14:13 字数 297 浏览 1 评论 0原文

(受到 nakiya 评论的启发)

许多 STL 算法将一个范围作为一对迭代器。例如,for_each(begin, end, &foo);。显然,如果 distance(begin, end) >= N,并且 begin 是一个随机访问迭代器,则 for_each(begin, begin+N, &foo); 仅将 foo 应用于前 N 个元素。

如果这两个条件中的任何一个不满足,现在是否有一个干净、通用的替代方案?

(Inspired by a comment from nakiya)

Many STL algorithms take a range as a pair of iterators. For instance, for_each(begin, end, &foo);. Obviously, if distance(begin, end) >= N, and begin is a random-access iterator, then for_each(begin, begin+N, &foo); applies foo only to the first N elements.

Now is there a clean, generic alternative if either of these two conditions is not met?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

開玄 2024-10-09 00:14:13

在不改变迭代器类型的情况下,不存在通用的完整解决方案。

证明:假设迭代器类型只是一个InputIterator,因此 begin 实际上引用(例如)一个流,而 end 是一个特殊情况的标记迭代器,它将一旦真实迭代器读取了 EOF,就与“真实”迭代器进行比较。

然后,任何使用 begin 尝试计算出 end 的新值以传递给算法的操作,都将“消耗”begin 的原始值>,因为这就是 InputIterator 的工作原理。

您可以做的是编写一个迭代器包装类,以便迭代器计算它已递增的次数,并在递增 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_inserterback_insert_iterator 执行相同的方式进行类型推导。但是,如果其参数类型是随机访问类别的迭代器,则帮助程序不应返回 FiniteIterator,而应返回 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)false,即使对于前向迭代器或更好的迭代器也是如此。有限迭代器仅在从相同起点创建时才具有可比性。

此外,这还没有完成。例如,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));

所以我离得不远,但这不是一个好的界面。我需要更多地考虑双向迭代器的情况。

There is no generic full solution without changing the iterator type.

Proof: suppose that the iterator type is only an InputIterator, so begin actually refers to (for example) a stream, and end is a special-case marker iterator, which will compare equal to the "real" iterator once the real iterator has read EOF.

Then any use of begin to try to work out a new value of end to pass to the algorithm, will "consume" the original value of begin, since that's how InputIterators work.

What you could do is write an iterator wrapper class, such that the iterator counts how many times it has been incremented, and compares equal to an "end" iterator once it has been incremented N times. N could be a template parameter, or a constructor parameter to one or other of the iterators.

Something like this. I've tested it compiles and works for me. Still to do - I'm currently only handling one of your two situations, "not a random-access iterator". I don't also handle the other, "distance < 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;
    }
};

Note that the second constructor only takes an iterator because the underlying type might not be default constructible (if it's only an InputIterator). In the case where the caller is creating an "end" iterator it doesn't use it, because it won't be valid once the other copy is incremented.

If the underlying iterator type is RandomAccess, then this wrapper isn't needed/wanted. So I provide a helper template function, that does the type deduction the same way back_inserter does for back_insert_iterator. However, in the case where its parameter type is an iterator of random-access category, the helper shouldn't return FiniteIterator<T>, but just 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);
}

Example usage (and minimal test):

#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";
}

Caution: finite_iterator(x, 1) == finite_iterator(++x, 0) is false, even for a forward iterator or better. Finite iterators are only comparable if they are created from the same starting point.

Also, this still isn't complete. For example std::reverse doesn't work, because for the purposes of accessing the referand, finite_iterator(x, 1) is "pointing at" x.

Currently the following happens to work:

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

So I'm not far off, but that's not a good interface. I would need to think more about the case of Bidirectional iterators.

奈何桥上唱咆哮 2024-10-09 00:14:13

已经有 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 是数字,因此进行后递增的效率并不低,在这种情况下它很有用。

There is already fill_n and generate_n, there is no foreach_n (or for_n would probably be more appropriate) but it is easy enough to write one.

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

You could do op(*begin++) but although it is less typing it may generate more code to copy the iterator. size_type is numeric so doing post-increment is no less efficient and here is a case where it is useful.

擦肩而过的背影 2024-10-09 00:14:13

我相信您可以创建一个类似于 boost::counting_iterator 的包装迭代器类型,它会将增量和底层迭代器保持在一起,并且一旦增量超过,就会与“结束”迭代器进行比较最大值。

I believe you could create a wrapper iterator type similar to boost::counting_iterator which would keep together both an increment and the underlying iterator, and would compare equal to an "end" iterator as soon as the increment exceeds the maximum value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文