std::vectorの要素の参照型はbool&ではない(場合がある)

std::vector::back() を使うと,末尾の要素の参照を取得できる.これは普通は,T&型だ.ところが,T=bool に限っては,「std::_Bit_reference」型なのだ(少なくとも g++ の場合).
通常,bool を関数の引数として渡すとき,わざわざ参照渡ししようとは思わないから(別に効率がよくなるわけではない),意識する必要はないのだが,テンプレート関数やクラスを作る場合には,知っておかないと理解不能なエラーに直面することがある.なので,忘備録も兼ねてメモ.

#include <vector>
using namespace std;
void Func(bool &x)
{
  x= false;
}
int main(int argc, char**argv)
{
  vector<bool> vec;
  vec.push_back(true);
  Func(vec.back());
  return 0;
}

これを,コンパイルすると,g++ の場合は,Func(vec.back()); に対して

error: invalid initialization of non-const reference of type ‘bool&’ from a temporary of type ‘std::_Bit_reference’

というエラーを吐く.
これは,bool 限定だ.bool を double や int に変更すれば,何の問題もなくコンパイルできる.

分析

このエラーは,冒頭で述べたとおり,std::vector の参照型が std::_Bit_reference であることに起因する.std::_Bit_reference 型は bool& に変換することができないから,Func(bool&) の引数として使うとエラーとなるのだ.
(ちゃんと調べていないが,おそらく bit** という名前から,bool の vector はビット演算を使った効率化をしているものと予想される.)

このような実装は,STL の仕様を満たしていないのではないか? と思ってしまうが,std::vector では,要素の値型 std::vector::value_type と要素の参照型 std::vector::reference の両方が独立に定義できるようになっているようで,必ずしも std::vector::value_type& と std::vector::reference が同じである必要はないようだ.(注:STLの仕様をちゃんと精査して書いていない)

実験

上記コードの void Func(bool &x) を

void Func(vector<bool>::reference x)

と変更すれば,コンパイルは通るし,期待通りに動く.

対策

ぱっと思いつく対策:

  • bool を参照渡しする必要は,通常ないと思われるので,値渡しする.
  • テンプレートで関数を共通化したい場合,std::vector については特殊化する.
  • もしくは,std::vector の参照を取得するために T& や const T& を使うのではなく,std::vector::reference や std::vector::const_reference を使う.

(追記)

  • 次のような bool のラッパを用いるのもひとつの手だ.
class TBool
{
public:
  TBool() : entity_(false) {}
  TBool(bool e) : entity_(e) {}
  TBool(const TBool &b) : entity_(b.entity_) {}
  operator bool&() {return entity_;}
  operator const bool&() const {return entity_;}
private:
  bool entity_;
};

std::vector などと使う.