通过转发的迭代循环的范围

Range for loop through forwarded iterable

假设我有以下情况

template<typename T>
void func(T&& arg)
{
    for (auto elem : arg) // ***
    {
        // do stuff
    }
}

其中 arg 是迭代器(向量或初始化列表等)。我对 *** 行感兴趣:以下哪项更好:

auto elem
auto& elem
`const` of the above

我想选项 (3) 包含在参数推导中(如果需要的话,auto 会在那里放置一个常量)。 func 也被称为

func(std::forward<T>(arg)); 

改变了什么?

这完全取决于容器中的内容。

如果容器的值是大的、重量级的对象,使用引用会更好,因为它可以避免复制它。

另一方面,如果容器的值只是小值,尤其是本机数据类型,则显式复制将鼓励更好的编译器优化,因为编译器可以假设在迭代期间不能使用当前值通过另一个引用或指针修改。

这完全取决于你想做什么以及 T 是什么类型。

你应该使用:

for (auto elem : arg)
{
      // do stuff
}

当 T 是基本类型(复制成本低)并且您不想修改 'arg' 集合时。

你应该使用:

for (auto& elem : arg)
{
      // do stuff
}

如果要修改 'arg' 集合。

你应该使用:

for (const auto& elem : arg)
{
      // do stuff
}

当 T 不是原始类型(复制成本高),并且您保证不更改 'arg' 集合。

在基于范围的 for 循环中使用 auto 作为变量声明几乎 总是 错误。只有在保证范围使用未突变的内置对象的情况下,它才是正确的。我通常不会在这里使用 auto(公平地说,我也不会首先使用基于范围的 for,但更喜欢算法)。

使用 auto& 将迭代器 it*it 的结果限制为 T&T const& 某些合适的类型 T。例如,它不允许迭代 std::vector<bool>。如果允许元素发生变异,那是一个合理的选择。使用 auto const&*it 的结果约束为左值或具有复制构造函数,并将防止范围发生突变。根据用途,这可能是一个合理的选择。

一般来说,默认选择应该是auto&&,因为除了不是void.

之外,它对*it的结果没有任何限制。

所以,总结一下:

  • for (auto elem: arg) 如果元素必须是内置的并且不发生变化。
  • for (auto& elem: arg) 如果元素是左值并且允许改变。
  • for (auto const& elem: arg) 如果元素是左值但未变异。
  • for (auto&& elem: arg) 在所有无关紧要的情况下。我认为总是使用 auto&& 是可以的。