広告

std::vector<bool>はテンプレート特殊化して別物になっている

タグ:c++

ある日何気なくstd::vectorを使っているとプログラムのデバッグ実行中に
Program: C:\WINDOWS\SYSTEM32\MSVCP140D.dll
File: c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.12.25827\include\vector
Line: 2219

Expression: vector<bool> iterator not dereferencable
  
のように"vector<bool> iterator not dereferencable"なるエラーが出てしまった。

Vectorのイテレータが参照解決できない?これはおかしいと思い調べていると、 C++ Reference vector_bool のようにstd::vectorはboolに関してのみ特殊な実装をしていることが分かった。


Bool型自体にはC++では処理系により4Byteだったり1Byteが割り当てられるのだが、 論理的には1bitあればよいので無駄である(実際はintとの互換性やcpuにとっての 扱いやすさのため多めにとっていると思われる)。  これをそのまま配列にしては一層無駄が大きいから、std::vectorでは bool 1つ1bitにしてbitarray的に格納するようだ。

しかしこの代償として、std::vector<bool>が内部に確保した領域を直接bool& などで参照することはできなくなっている。
#include <vector>
#include <iostream>
int main()
{
  std::vector<bool> vec{true, false, true, false};
  for(auto it = vec.begin(); it != vec.end(); it++)
  {
    bool& b = *it;  // これはできない
    b = true;
    std::cout << *it << std::endl;
  }
}
  

g++だと上記のコードはちゃんとコンパイルエラーにしてくれている:
  main.cpp: In function ‘int main()’:
  main.cpp:8:15: error: cannot bind non-const lvalue reference of type ‘bool&’ to an rvalue of type ‘bool’
       bool& b = *it;
                 ^~~
  In file included from /usr/include/c++/7/vector:65:0,
                   from main.cpp:1:
  /usr/include/c++/7/bits/stl_bvector.h:80:5: note:   after user-defined conversion: std::_Bit_reference::operator bool() const
       operator bool() const _GLIBCXX_NOEXCEPT
       ^~~~~~~~
  


この問題の手っ取り早い対策としては、メモリ消費が問題でなければbool値も std::vector<char>などにキャストして格納してしまえばよい。
上のコードもbool -> charの置き換えで問題なく動作した。

なおVisual Studioではこのassertが暴走していることがあるようで、単なる out-of-range index (長さ10の配列の11番目の要素にアクセスした) で "vector<bool> iterator not dereferencableの エラーが出ていた (ダメじゃん)。