基于范围的“for”循环是否破坏了许多简单的算法?

时间:2022-08-02 14:29:02

Algorithm solution:

算法的解决方案:

std::generate(numbers.begin(), numbers.end(), rand);

Range-based for-loop solution:

基于范围循环解决方案:

for (int& x : numbers) x = rand();

Why would I want to use the more verbose std::generate over range-based for-loops in C++11?

为什么我要在c++ 11中使用更详细的std::生成基于范围的for循环?

10 个解决方案

#1


77  

The first version

第一个版本

std::generate(numbers.begin(), numbers.end(), rand);

tells us that you want to generate a sequence of values.

告诉我们要生成一个值序列。

In the second version the reader will have to figure that out himself.

在第二版中,读者必须自己弄清楚。

Saving on typing is usually suboptimal, as it is most often lost in reading time. Most code is read a lot more than it is typed.

节省打字时间通常不是最理想的,因为它通常会浪费在阅读时间上。大多数代码的读取量要比输入量大得多。

#2


42  

Whether the for loop is range based or not does not make a difference at all, it only simplifies the code inside the parenthesis. Algorithms are clearer in that they show the intent.

无论for循环是否基于范围,都没有什么区别,它只是简化了括号内的代码。算法更清晰,因为它们显示了意图。

#3


30  

Personally, my initial reading of:

就我个人而言,我最初的解读是:

std::generate(numbers.begin(), numbers.end(), rand);

is "we're assigning to everything in a range. The range is numbers. The values assigned are random".

是“我们给范围内的所有东西赋值。”范围是数字。分配的值是随机的。

My initial reading of:

我最初的阅读:

for (int& x : numbers) x = rand();

is "we're doing something to everything in a range. The range is numbers. What we do is assign a random value."

是“我们正在对范围内的一切做一些事情。”范围是数字。我们要做的是分配一个随机值

Those are pretty darn similar, but not identical. One plausible reason I might want to provoke the first reading, is because I think the most important fact about this code is that it assigns to the range. So there's your "why would I want to...". I use generate because in C++ std::generate means "range assignment". As btw does std::copy, the difference between the two is what you're assigning from.

它们很相似,但不完全相同。我可能想引起第一次阅读的一个貌似合理的原因是,我认为关于这段代码最重要的事实是它分配给了范围。这就是你的“为什么我想……”我使用generate是因为在c++ std::generate意思是“范围分配”。顺便说一句,std::复制,这两者之间的区别是你要从哪个分配。

There are confounding factors, though. Range-based for loops have an inherently more direct way of expressing that the range is numbers, than iterator-based algorithms do. That's why people work on range-based algorithm libraries: boost::range::generate(numbers, rand); looks better than the std::generate version.

不过,也有一些令人困惑的因素。与基于迭代器的算法相比,基于范围的for循环有一种更直接的方式来表示范围是数字。这就是为什么人们使用基于范围的算法库:boost::range::generate(number, rand);看起来比std::生成版本更好。

As against that, int& in your range-based for loop is a wrinkle. What if the value type of the range isn't int, then we're doing something annoyingly subtle here that depends on it being convertible to int&, whereas the generate code only depends on the return from rand being assignable to the element. Even if the value type is int, I still might stop to think about whether it is or not. Hence auto, which defers thinking about the types until I see what gets assigned -- with auto &x I say "take a reference to the range element, whatever type that might have". Back in C++03, algorithms (because they're function templates) were the way to hide exact types, now they're a way.

与此相反,在基于范围的for循环中,int&是一个问题。如果范围的值类型不是int类型,那么我们在这里做一些令人讨厌的微妙的事情,这取决于它是否可转换为int&,而生成代码只取决于从rand返回到元素的可赋值。即使值类型是int类型,我仍然可能会停下来考虑它是否为int类型。因此auto,它会考虑类型,直到我看到分配了什么——使用auto &x,我会说“引用range元素,不管它是什么类型”。在c++ 03中,算法(因为它们是函数模板)是隐藏精确类型的方法,现在它们是一种方法。

I think it has always been the case that the simplest algorithms have only a marginal benefit over the equivalent loops. Range-based for loops improve loops (primarily by removing most of the boilerplate, although there's a little more to them than that). So the margins draw tighter and perhaps you change your mind in some specific cases. But there's a still a style difference there.

我认为最简单的算法和等价循环相比只有边际效益。基于范围的for循环改进了循环(主要是通过删除大部分样板文件,尽管它们的功能不止这些)。所以边距越拉越紧,在某些特定情况下你可能会改变主意。但风格上还是有差异的。

#4


22  

In my opinion, Effective STL Item 43: "Prefer algorithm calls to hand-written loops." is still a good advice.

在我看来,有效的STL第43项:“比起手写的循环,更喜欢算法调用”仍然是一个很好的建议。

I usually write wrapper functions to get rid of the begin() / end() hell. If you do that, your example will look like this:

我通常编写包装函数来避免开始()/结束()的麻烦。如果你这样做,你的例子会是这样的:

my_util::generate(numbers, rand);

I believe it beats the range based for loop both in communicating the intent and in readability.

我相信它在传达意图和可读性方面都优于基于for循环的范围。


Having said that, I must admit that in C++98 some STL algorithm calls yielded unutterable code and following "Prefer algorithm calls to hand-written loops" did not seem like a good idea. Luckily, lambdas have changed that.

话虽如此,我必须承认,在c++ 98中,一些STL算法调用产生了不可言传的代码,遵循“更喜欢算法调用而不是手工编写的循环”似乎不是个好主意。幸运的是,lambdas改变了这一点。

Consider the following example from Herb Sutter: Lambdas, Lambdas Everywhere.

考虑一下Herb Sutter的以下示例:Lambdas, Lambdas无处不在。

Task: Find first element in v that is > x and < y.

任务:找到v中的第一个元素> x和< y。

Without lambdas:

没有λ:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

With lambda

和λ

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

#5


21  

In my opinion, the manual loop, though might reduce verbosity, lacks readabitly:

在我看来,手动循环虽然可以减少冗长,但缺乏可读性:

for (int& x : numbers) x = rand();

I would not use this loop to initialize1 the range defined by numbers, because when I look at it, it seems to me that it is iterating over a range of numbers, but in actuality it does not (in essence), i.e instead of reading from the range, it is writing to the range.

我不会使用这个循环来初始化由数字定义的范围,因为当我看到它时,在我看来,它正在迭代一个数字范围,但实际上它不是(本质上)I。e不是从范围中阅读,而是从范围中写作。

The intent is much clearer when you use std::generate.

当您使用std::generate时,意图更加清晰。

1. initialize in this context means to give meaningful value to the elements of the container.

1。在此上下文中初始化意味着为容器的元素提供有意义的值。

#6


9  

There are some things you cannot do (simply) with range-based loops that algorithms that take iterators as input can. For example with std::generate:

有些事情您不能(简单地)使用基于区间的循环,即以迭代器为输入的算法。例如与std::生成:

Fill the container up to limit (excluded, limit is a valid iterator on numbers) with variables from one distribution and the rest with variables from another distribution.

用来自一个分布的变量填充容器(排除,limit是一个有效的数字迭代器),用来自另一个分布的变量填充其余的容器。

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

Iterator-based algorithms give you a better control on the range you are operating on.

基于迭代器的算法可以更好地控制操作范围。

#7


6  

For the particular case of std::generate, I agree with the previous answers on readability/intent issue. std::generate seems a more clear version to me. But I admit that this is in a way a matter of taste.

对于std::generate的特殊情况,我同意之前关于可读性/意图问题的回答。生成对我来说是一个更清晰的版本。但我承认,这在某种程度上是一个品味问题。

That said, I've have another reason to not throw away the std::algorithm - there are certain algorithms which are specialized for some data types.

也就是说,我有另一个理由不抛弃std::算法——有某些算法专门用于某些数据类型。

The simplest example would be std::fill. The general version is implemented as a for-loop over the provided range, and this version will be used when instantiating the template. But not always. E.g. if you'll provide it a range which is a std::vector<int> - often it will actually call memset under the hood, yielding a much faster and better code.

最简单的例子是std::fill。通用版本在提供的范围内实现为for循环,在实例化模板时将使用该版本。但并非总是如此。如果你提供的是一个std::vector —通常它会在引擎盖下面调用memset,产生更快更好的代码。

So I'm trying to play an efficiency card here.

所以我想在这里打出效率牌。

Your hand-written loop might be as fast as a std::algorithm version, but it can hardly be faster. And more than that, std::algorithm may be specialized for particular containers and types and it's done under the clean STL interface.

您的手写循环可能与std::算法版本一样快,但它几乎不可能更快。更重要的是,std::算法可能专门用于特定的容器和类型,它是在干净的STL接口下完成的。

#8


3  

My answer would be maybe and no. If we're talkinng about C++11, then maybe (more like no). For example std::for_each is really annoying to use even with lambdas:

我的答案可能是否定的。如果我们谈论的是c++ 11,那么也许(更像是no)。例如std::for_each即使使用lambdas也很烦人:

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

But using range-based for is a lot better:

但是使用基于范围的for更好:

for (auto& x : c)
{
    // do stuff with x
}

On the other hand, if we're talking about C++1y, then I would argue that no, the algorithms will not be obsoleted by range based for. In C++ standard committee there is a study group that is working on a proposal to add ranges to C++, and also there is work being done on polymorphic lambdas. Ranges would remove the need to use pair of iterators and polymorphic lambda would let you to not specify exact argument type of lambda. This means that std::for_each could be used like this (don't take this as a hard fact, it's just what the dreams look like today):

另一方面,如果我们讨论的是c++ 1y,那么我认为不,算法不会被基于range的。在c++标准委员会中,有一个研究小组正在研究向c++添加范围的建议,同时还有关于多态lambdas的工作。范围将消除使用一对迭代器的需要,而多态lambda使您不必指定lambda的确切参数类型。这意味着std::for_each可以这样使用(不要把这当作一个很难的事实,这就是今天的梦想):

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

#9


1  

One thing that should be noticed is that an algorithm express what is done, not how.

需要注意的一件事是,算法表达的是已经做了什么,而不是如何做。

Range-based loop include the way things are done: start with the first, apply and go next element until the end. Even a simple algorithm could do things differently (at least some overloads for specific containers, not even thinking about the horrible vector), and at least the way it is done is not the writer business.

基于范围的循环包括了事情的完成方式:从第一个开始,应用到下一个元素直到结束。即使是一个简单的算法也可以做不同的事情(至少对特定的容器有一些重载,甚至不考虑可怕的向量),而且至少这样做的方式与作者无关。

TO me that's much of the difference, encapsulate as much as you can, and that justifies the sentence when you can, use algorithms.

对我来说,这是很大的不同,尽可能地概括,这就证明了这个句子的合理性,你可以使用算法。

#10


1  

Range-based for-loop is just that. Until of course standard is changed.

基于范围的for循环就是这样。当然,直到标准被改变。

Algorithm is a function. A function which puts some requirements on its parameters. The requirements are phrased in a standard to allow for example implementation that takes advantage of all available execution threads and will speed you up automatically.

算法是一个函数。对其参数提出一些要求的函数。这些需求是在一个标准中描述的,例如实现可以利用所有可用的执行线程并自动加速。

#1


77  

The first version

第一个版本

std::generate(numbers.begin(), numbers.end(), rand);

tells us that you want to generate a sequence of values.

告诉我们要生成一个值序列。

In the second version the reader will have to figure that out himself.

在第二版中,读者必须自己弄清楚。

Saving on typing is usually suboptimal, as it is most often lost in reading time. Most code is read a lot more than it is typed.

节省打字时间通常不是最理想的,因为它通常会浪费在阅读时间上。大多数代码的读取量要比输入量大得多。

#2


42  

Whether the for loop is range based or not does not make a difference at all, it only simplifies the code inside the parenthesis. Algorithms are clearer in that they show the intent.

无论for循环是否基于范围,都没有什么区别,它只是简化了括号内的代码。算法更清晰,因为它们显示了意图。

#3


30  

Personally, my initial reading of:

就我个人而言,我最初的解读是:

std::generate(numbers.begin(), numbers.end(), rand);

is "we're assigning to everything in a range. The range is numbers. The values assigned are random".

是“我们给范围内的所有东西赋值。”范围是数字。分配的值是随机的。

My initial reading of:

我最初的阅读:

for (int& x : numbers) x = rand();

is "we're doing something to everything in a range. The range is numbers. What we do is assign a random value."

是“我们正在对范围内的一切做一些事情。”范围是数字。我们要做的是分配一个随机值

Those are pretty darn similar, but not identical. One plausible reason I might want to provoke the first reading, is because I think the most important fact about this code is that it assigns to the range. So there's your "why would I want to...". I use generate because in C++ std::generate means "range assignment". As btw does std::copy, the difference between the two is what you're assigning from.

它们很相似,但不完全相同。我可能想引起第一次阅读的一个貌似合理的原因是,我认为关于这段代码最重要的事实是它分配给了范围。这就是你的“为什么我想……”我使用generate是因为在c++ std::generate意思是“范围分配”。顺便说一句,std::复制,这两者之间的区别是你要从哪个分配。

There are confounding factors, though. Range-based for loops have an inherently more direct way of expressing that the range is numbers, than iterator-based algorithms do. That's why people work on range-based algorithm libraries: boost::range::generate(numbers, rand); looks better than the std::generate version.

不过,也有一些令人困惑的因素。与基于迭代器的算法相比,基于范围的for循环有一种更直接的方式来表示范围是数字。这就是为什么人们使用基于范围的算法库:boost::range::generate(number, rand);看起来比std::生成版本更好。

As against that, int& in your range-based for loop is a wrinkle. What if the value type of the range isn't int, then we're doing something annoyingly subtle here that depends on it being convertible to int&, whereas the generate code only depends on the return from rand being assignable to the element. Even if the value type is int, I still might stop to think about whether it is or not. Hence auto, which defers thinking about the types until I see what gets assigned -- with auto &x I say "take a reference to the range element, whatever type that might have". Back in C++03, algorithms (because they're function templates) were the way to hide exact types, now they're a way.

与此相反,在基于范围的for循环中,int&是一个问题。如果范围的值类型不是int类型,那么我们在这里做一些令人讨厌的微妙的事情,这取决于它是否可转换为int&,而生成代码只取决于从rand返回到元素的可赋值。即使值类型是int类型,我仍然可能会停下来考虑它是否为int类型。因此auto,它会考虑类型,直到我看到分配了什么——使用auto &x,我会说“引用range元素,不管它是什么类型”。在c++ 03中,算法(因为它们是函数模板)是隐藏精确类型的方法,现在它们是一种方法。

I think it has always been the case that the simplest algorithms have only a marginal benefit over the equivalent loops. Range-based for loops improve loops (primarily by removing most of the boilerplate, although there's a little more to them than that). So the margins draw tighter and perhaps you change your mind in some specific cases. But there's a still a style difference there.

我认为最简单的算法和等价循环相比只有边际效益。基于范围的for循环改进了循环(主要是通过删除大部分样板文件,尽管它们的功能不止这些)。所以边距越拉越紧,在某些特定情况下你可能会改变主意。但风格上还是有差异的。

#4


22  

In my opinion, Effective STL Item 43: "Prefer algorithm calls to hand-written loops." is still a good advice.

在我看来,有效的STL第43项:“比起手写的循环,更喜欢算法调用”仍然是一个很好的建议。

I usually write wrapper functions to get rid of the begin() / end() hell. If you do that, your example will look like this:

我通常编写包装函数来避免开始()/结束()的麻烦。如果你这样做,你的例子会是这样的:

my_util::generate(numbers, rand);

I believe it beats the range based for loop both in communicating the intent and in readability.

我相信它在传达意图和可读性方面都优于基于for循环的范围。


Having said that, I must admit that in C++98 some STL algorithm calls yielded unutterable code and following "Prefer algorithm calls to hand-written loops" did not seem like a good idea. Luckily, lambdas have changed that.

话虽如此,我必须承认,在c++ 98中,一些STL算法调用产生了不可言传的代码,遵循“更喜欢算法调用而不是手工编写的循环”似乎不是个好主意。幸运的是,lambdas改变了这一点。

Consider the following example from Herb Sutter: Lambdas, Lambdas Everywhere.

考虑一下Herb Sutter的以下示例:Lambdas, Lambdas无处不在。

Task: Find first element in v that is > x and < y.

任务:找到v中的第一个元素> x和< y。

Without lambdas:

没有λ:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

With lambda

和λ

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

#5


21  

In my opinion, the manual loop, though might reduce verbosity, lacks readabitly:

在我看来,手动循环虽然可以减少冗长,但缺乏可读性:

for (int& x : numbers) x = rand();

I would not use this loop to initialize1 the range defined by numbers, because when I look at it, it seems to me that it is iterating over a range of numbers, but in actuality it does not (in essence), i.e instead of reading from the range, it is writing to the range.

我不会使用这个循环来初始化由数字定义的范围,因为当我看到它时,在我看来,它正在迭代一个数字范围,但实际上它不是(本质上)I。e不是从范围中阅读,而是从范围中写作。

The intent is much clearer when you use std::generate.

当您使用std::generate时,意图更加清晰。

1. initialize in this context means to give meaningful value to the elements of the container.

1。在此上下文中初始化意味着为容器的元素提供有意义的值。

#6


9  

There are some things you cannot do (simply) with range-based loops that algorithms that take iterators as input can. For example with std::generate:

有些事情您不能(简单地)使用基于区间的循环,即以迭代器为输入的算法。例如与std::生成:

Fill the container up to limit (excluded, limit is a valid iterator on numbers) with variables from one distribution and the rest with variables from another distribution.

用来自一个分布的变量填充容器(排除,limit是一个有效的数字迭代器),用来自另一个分布的变量填充其余的容器。

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

Iterator-based algorithms give you a better control on the range you are operating on.

基于迭代器的算法可以更好地控制操作范围。

#7


6  

For the particular case of std::generate, I agree with the previous answers on readability/intent issue. std::generate seems a more clear version to me. But I admit that this is in a way a matter of taste.

对于std::generate的特殊情况,我同意之前关于可读性/意图问题的回答。生成对我来说是一个更清晰的版本。但我承认,这在某种程度上是一个品味问题。

That said, I've have another reason to not throw away the std::algorithm - there are certain algorithms which are specialized for some data types.

也就是说,我有另一个理由不抛弃std::算法——有某些算法专门用于某些数据类型。

The simplest example would be std::fill. The general version is implemented as a for-loop over the provided range, and this version will be used when instantiating the template. But not always. E.g. if you'll provide it a range which is a std::vector<int> - often it will actually call memset under the hood, yielding a much faster and better code.

最简单的例子是std::fill。通用版本在提供的范围内实现为for循环,在实例化模板时将使用该版本。但并非总是如此。如果你提供的是一个std::vector —通常它会在引擎盖下面调用memset,产生更快更好的代码。

So I'm trying to play an efficiency card here.

所以我想在这里打出效率牌。

Your hand-written loop might be as fast as a std::algorithm version, but it can hardly be faster. And more than that, std::algorithm may be specialized for particular containers and types and it's done under the clean STL interface.

您的手写循环可能与std::算法版本一样快,但它几乎不可能更快。更重要的是,std::算法可能专门用于特定的容器和类型,它是在干净的STL接口下完成的。

#8


3  

My answer would be maybe and no. If we're talkinng about C++11, then maybe (more like no). For example std::for_each is really annoying to use even with lambdas:

我的答案可能是否定的。如果我们谈论的是c++ 11,那么也许(更像是no)。例如std::for_each即使使用lambdas也很烦人:

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

But using range-based for is a lot better:

但是使用基于范围的for更好:

for (auto& x : c)
{
    // do stuff with x
}

On the other hand, if we're talking about C++1y, then I would argue that no, the algorithms will not be obsoleted by range based for. In C++ standard committee there is a study group that is working on a proposal to add ranges to C++, and also there is work being done on polymorphic lambdas. Ranges would remove the need to use pair of iterators and polymorphic lambda would let you to not specify exact argument type of lambda. This means that std::for_each could be used like this (don't take this as a hard fact, it's just what the dreams look like today):

另一方面,如果我们讨论的是c++ 1y,那么我认为不,算法不会被基于range的。在c++标准委员会中,有一个研究小组正在研究向c++添加范围的建议,同时还有关于多态lambdas的工作。范围将消除使用一对迭代器的需要,而多态lambda使您不必指定lambda的确切参数类型。这意味着std::for_each可以这样使用(不要把这当作一个很难的事实,这就是今天的梦想):

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

#9


1  

One thing that should be noticed is that an algorithm express what is done, not how.

需要注意的一件事是,算法表达的是已经做了什么,而不是如何做。

Range-based loop include the way things are done: start with the first, apply and go next element until the end. Even a simple algorithm could do things differently (at least some overloads for specific containers, not even thinking about the horrible vector), and at least the way it is done is not the writer business.

基于范围的循环包括了事情的完成方式:从第一个开始,应用到下一个元素直到结束。即使是一个简单的算法也可以做不同的事情(至少对特定的容器有一些重载,甚至不考虑可怕的向量),而且至少这样做的方式与作者无关。

TO me that's much of the difference, encapsulate as much as you can, and that justifies the sentence when you can, use algorithms.

对我来说,这是很大的不同,尽可能地概括,这就证明了这个句子的合理性,你可以使用算法。

#10


1  

Range-based for-loop is just that. Until of course standard is changed.

基于范围的for循环就是这样。当然,直到标准被改变。

Algorithm is a function. A function which puts some requirements on its parameters. The requirements are phrased in a standard to allow for example implementation that takes advantage of all available execution threads and will speed you up automatically.

算法是一个函数。对其参数提出一些要求的函数。这些需求是在一个标准中描述的,例如实现可以利用所有可用的执行线程并自动加速。