在基于范围的for循环中使用通用引用有什么好处?

Ali*_*Ali 111 c++ performance for-loop move-semantics c++11

const auto&如果我想执行只读操作就足够了.但是,我已经碰到了

for (auto&& e : v)  // v is non-const
Run Code Online (Sandbox Code Playgroud)

最近几次.这让我想知道:

是否有可能在一些不起眼的角落情况下,存在使用通用引用一些性能优势,相比auto&还是const auto&

(shared_ptr是一个晦涩角落案件的嫌疑人)


更新 我在收藏夹中找到的两个示例:

迭代基本类型时使用const引用的任何缺点?
我可以使用基于范围的for循环轻松迭代地图的值吗?

请专注于一个问题:我为什么要在使用自动&&范围为基础的for循环?

How*_*ant 87

我能看到的唯一优势是当序列迭代器返回一个代理引用时,你需要以非const方式对该引用进行操作.例如考虑:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}
Run Code Online (Sandbox Code Playgroud)

这不会编译,因为vector<bool>::referenceiterator将返回的rvalue 不会绑定到非const左值引用.但这会奏效:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}
Run Code Online (Sandbox Code Playgroud)

所有这一切,除非你知道你需要满足这样一个用例,否则我不会这样编码.即我不会无偿地这样做,因为它确实让人们想知道你在做什么.如果我这样做了,那么为什么包括评论也不会有什么坏处:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}
Run Code Online (Sandbox Code Playgroud)

编辑

我的最后一个案例应该是一个有意义的模板.如果你知道循环总是处理代理引用,那么auto就可以了auto&&.但是当循环有时处理非代理引用和有时代理引用时,我认为auto&&它将成为首选的解决方案.

  • 我个人喜欢在通用代码中使用`auto &&`,我需要修改序列的元素.如果我不这样做,我会坚持`auto const&`. (28认同)
  • 当我希望编译器帮助我检查我是否不小心修改序列中的元素时,我更喜欢`const auto&`. (7认同)
  • @Xeo:+1正是因为喜欢自己的爱好者,不断尝试和推动更好的做事方式,C++不断发展.谢谢.:-) (7认同)
  • 编写不必要地混淆人们的代码的另一个术语是:编写混淆代码.最好让你的代码尽可能简单,但并不简单.这将有助于减少错误数量.话虽如此,随着&&变得越来越熟悉,那么也许5年后人们会期待一种自动和成语(假设它实际上没有任何伤害).我不知道这是否会发生.但简直就是旁观者的眼睛,如果你的写作不仅仅是你自己,那么请考虑你的读者. (6认同)
  • 另一方面,没有明确的_disadvantage_,有吗?(除了可能令人困惑的人,我个人认为这不值得一提.) (3认同)
  • @HowardHinnant,谢谢,我刚刚观看了 Scott Meyers 关于此问题的演示,它对此非常有帮助,并意识到我误解了右值与右值引用。我不明白为什么通用引用实际上不是一个单独的东西,而是带有像“&amp;&amp;&amp;”这样的新标记的标准的一部分? (3认同)
  • @LightnessRacesinOrbit:`template <class T> T const&as_const(T const&v){return v; }`和`for(auto&e:as_const(v))...`. (2认同)
  • @LightnessRacesinOrbit `std::begin` 将委托给调用成员 `begin`,该成员在 `const` 上重载。 (2认同)
  • @乔纳森:这是当时做出的判断:一石二鸟。 (2认同)
  • “[...] 那么也许 5 年后人们会开始期待 auto&amp;&amp; 成语 [...]” 7 年后 for(auto&amp;&amp; x : y) 仍然非常令人困惑,绝对不是预期的成语。 (2认同)

Die*_*ühl 25

使用auto&&普遍引用了基于范围的for-loop的优点是你捕捉你所得到的.对于大多数类型的迭代器,你可能会得到无论是T&T const&某种类型T.有趣的情况是,取消引用迭代器会产生一个临时的:C++ 2011得到了宽松的要求,并且迭代器不一定需要产生左值.通用引用的使用与以下参数转发相匹配std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}
Run Code Online (Sandbox Code Playgroud)

该函数对象f可以治疗T&,T const&以及T不同.为什么基于范围的for循环体不同?当然,要实际利用使用通用引用推断出类型,您需要相应地传递它们:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}
Run Code Online (Sandbox Code Playgroud)

当然,使用std::forward()意味着您接受要移动的任何返回值.这样的对象在非模板代码中是否有用,我不知道(还是?).我可以想象使用通用引用可以为编译器提供更多信息来做正确的事情.在模板化代码中,它不会对对象应该发生什么做出任何决定.


Pup*_*ppy 8

我几乎总是使用auto&&.当你不需要时,为什么会被边缘情况所困扰?键入也更短,我发现它更透明.当你使用时auto&& x,你就知道每次x都是这样*it.

  • 我的问题是,如果`const auto&'就足够了,你就放弃`const`-ness和`auto &&`.这个问题要求我可以被咬伤的角落案件.什么是Dietmar或霍华德尚未提及的角落案件? (29认同)
  • 如果你_do_使用`auto&amp;&amp;`,这是一种被咬的方法。如果您正在捕获的类型应该在循环体中移动(例如),并更改为以后的日期以便它解析为 `const&amp;` 类型,则您的代码将默默地继续运行,但您的移动将会到来副本。这段代码会非常具有欺骗性。但是,如果您将类型明确指定为 r 值引用,那么更改容器类型的任何人都会收到编译错误,因为您确实真的希望这些对象被移动而不是被复制...... (3认同)
  • @JerryMa 这取决于你对类型的了解。如上所述,“auto&amp;&amp;”将提供“通用参考”,因此除此之外的任何内容都会为您提供更具体的类型。如果假设“v”是“向量”,则可以执行“decltype(v)::value_type&amp;&amp;”,这就是我假设您想要通过在迭代器类型上获取“operator*”的结果来实现的。您还可以执行 `decltype(begin(v))::value_type&amp;&amp;` 来检查迭代器的类型而不是容器。不过,如果我们对类型了解甚少,我们可能会认为使用“auto&amp;&amp;”会更清楚一些...... (2认同)