Range-for-loops 和 std::vector<bool>

Range-for-loops and std::vector<bool>

本文关键字:bool gt lt vector std Range-for-loops      更新时间:2023-10-16

为什么这个代码能在中工作

std::vector<int> intVector(10);
for(auto& i : intVector)
    std::cout << i;

这不是吗?

std::vector<bool> boolVector(10);
for(auto& i : boolVector)
    std::cout << i;

在后一种情况下,我得到一个错误

错误:类型为"std::_Bit_reference&"的非常量引用的初始化无效来自类型为"std::_Bit_iterator::reference{aka std::_Bit_reference}"的右值

for(auto& i : boolVector)

因为std::vector<bool>不是容器!

std::vector<T>的迭代器通常取消对T&的引用,您可以将其绑定到自己的auto&

然而,std::vector<bool>将其bool封装在整数中,因此在访问它们时需要一个代理来进行位屏蔽。因此,它的迭代器返回一个Proxy
由于返回的Proxy是一个prvalue(临时),因此它无法绑定到诸如auto&之类的左值引用。

解决方案:使用auto&&,如果给它一个左值引用,它将正确地折叠成左值引用;或者,如果给了它一个代理,它将绑定并保持临时活动。

std::vector<bool>不遵守标准容器规则。

特别地,它的迭代器的operator*不返回bool&

无效代码中的循环

#include <vector>
#include <iostream>
int main() {
  std::vector<bool> boolVector(10);
  for (auto& i: boolVector)
      std::cout << i;
}

可以通过以下三种方式中的任何一种重写以迭代值:

  1. (只读)

     for (auto const i: boolVector)
         std::cout << i;
    
  2. (只读,可能效率低下)

     for (auto const& i: boolVector)  
         std::cout << i;
    
  3. (读/写²)

     for (auto&& i: boolVector)
         std::cout << i;
    

在第一个和最后一个之间的选择取决于您是需要修改向量中的值,还是只读取它们。


注:

  1. 我说"可能效率低";因为它具有不必要的间接性。任何优秀的优化器都应该创建与第一个示例相同的代码。

  2. for (auto i: boolVector)还提供了向量的读/写视图(因为我们已经复制了代理对象)。但我不建议这样做,因为读者可能会认为这样的写作只会产生局部效果,就像标准容器一样。

    这就是为什么我在只读情况下使用auto const i;或者,我们可以使用boolVector | std::views::as_const(由于C++23)或通过使用对向量的const引用(for (auto const& v = boolVector; auto i: v))来获得const的代理。

vector<bool>(通常)专门用于将每个bool存储在一个位中,从而将存储成本从每个值一个字节降低到每八个值一个比特。据我所知,目前没有一个处理器是位可寻址的,因此不可能在vector<bool>中存储对值的引用。迭代值i需要使用普通的auto,而不是auto&