为什么总是在 std::for_each 中指定迭代器?

发布于 10-31 05:59 字数 579 浏览 5 评论 0原文

据我所知,迭代 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;
  }
}

当然,这可能是这是惯用的做事方式,但我从来没有注意到我是 C++ 新手。)

As far as I can tell, the idiom for iterating over STL collections looks something like this:

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

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

Specifying the first and last iterators is useful if I want to only work on a certain range of the collection, or do something more creative, but most of the time, I suspect we're actually wanting to work with the whole collection. So, I'm wondering why people bother specifying the iterators in that situation (since they'll always be the same), and don't just use a convenience function along these lines:

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 course, it's possible that this is the idiomatic way of doing things and I never noticed! I'm new to C++, though.)

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

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

发布评论

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

评论(4

泪是无色的血2024-11-07 05:59:57

我绝对对 STL 非常着迷。但我不记得实际上曾经使用过for_each

习惯用法是

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

C++11 引入糖将其减少为 这

for ( auto elem : coll )

与您的便利函数类似,但使用免费(非成员)std::beginstd::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 )

I'm totally ga-ga for STL, absolutely. But I can't recall actually ever using for_each.

The idiom is

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

C++11 introduces sugar to reduce this to

for ( auto elem : coll )

This is similar to your convenience function, but uses free (non-member) std::begin and std::end functions that allow compatibility with objects that are not standard containers.

Also, looking it up (I haven't gotten to play with this as it's not in GCC yet), it looks like it restricts the programmer to accessing elements of the range, not iterators into it.


As for using the container to refer to the entirety of its range, it's much preferable to maintain the flexibility to allow subranges. The alternative solution is to introduce an idiom for a pair of iterators, { begin, end }. There was some debate, and I expected C++11 to include a feature such that

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

but upon reading the Standard it looks like pair lacks this functionality.

You can create your own such pair, however, to obtain the flexible range-based 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 )
萌酱2024-11-07 05:59:57

指定开始和结束迭代器而不仅仅是集合确实很乏味且容易出错(例如您的示例代码错误地尝试调用 .begin().end()a 而不是 v 上)。这就是发明 Boost Range 的原因。有了它,您可以编写如下代码:

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

它还引入了范围适配器的概念,可以将其与算法组合以倍增其有用性和通用性。

[推测] STL 使用迭代器而不是范围的原因是,它是从构造算法、然后找到这些算法的最低要求并根据这些要求来表达它们的角度来构思的。算法需要迭代器才能完成它们的工作,尽管算法使用的自然对象实际上是值的范围。正如另一个答案中提到的,STL 面临着严重的时间压力,因此这些问题从未得到解决。幸运的是,我们现代人有 Boost.Range,所以我们可以使用基于范围的算法。

Specifying the begin and end iterators instead of just the collection is indeed tedious and error prone (for example your example code erroneously tried to call .begin() and .end() on a instead of v). That is why Boost Range was invented. With it you are able to write code such as:

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

It also introduces the notion of range adaptors, which can be composed with algorithms to multiply their usefulness and generality.

[Speculation] The reason that STL uses iterators instead of ranges is that it was conceived from the viewpoint of constructing algorithms and then finding the minimum requirements for those algorithms and expressing them in terms of those requirements. Algorithms require iterators in order do their job, even though the natural thing that algorithms are used on are actually ranges of values. As was mentioned in another answer the STL was under severe time pressure, and so these issues were never worked out. Luckily us modern folks have Boost.Range and so we can use range based algorithms.

葮薆情2024-11-07 05:59:57

你的建议没有任何问题,但我不得不说 Boost.ForEach 是当今 C++ 世界中最接近“惯用”的东西。

这种方法的好处(在使用中看起来类似于:)

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

是您还可以内联操作,而不是严格绑定到显式函数映射。

There is nothing wrong with what you suggest, though I'd have to say that Boost.ForEach is about as close to "idiomatic" as things get in the present-day C++ universe.

The benefit of this approach, which in use looks something like:

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

is that you can also inline your operations and not be bound strictly to an explicit functional mapping.

你的呼吸2024-11-07 05:59:57

原因在于STL的设计者来自理论方面。在 Comp.Sci 中,范围是比容器更基本的概念。在实践中,容器更为常见。 STL 是在 1996-1998 年迫于严重的时间压力而添加的,并且没有进行积极的重构。

您会在 std::for_each 的第三个参数中看到类似的问题。理论上,lambda 是存在的。在 C++98 中,他们没有。因此,您必须定义一个不合规矩的函子,使用带有绑定器和合成器的详细语法,或者使用无状态函数指针。

就我个人而言,我认为所有STL算法都应该采用一个范围(单个对象),而不是一对迭代器。将后者包装在一个范围内很简单。现在您看到 ostream_iterators 必须定义一个相当任意的最终对象。

The reason is that the designers of the STL came from the theoretical side. In Comp.Sci, a range is a more fundamental concept than a container. In practice, containers are far more common. The STL was added under serious time pressure back in 1996-1998, and it wasn't refactored aggressively.

You see a similar problem with the third argument of std::for_each. In theory, lambda's exist. In C++98, they didn't. Therefore you had to either define a functor out of line, use a verbose syntax with binders and compositors, or use a stateless pointer-to-function.

Personally, I think all STL algorithms should have taken a range (single object), not a pair of iterators. It's trivial to wrap the latter in a range. Now you see that ostream_iterators have to define a rather arbitrary end object.

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