Safe bool イディオム

というのを、たまたま知りました。聞いたことがなかったので、「なんじゃそりゃ?」とGoogle先生に伺いを立ててみたところ、こういうものらしいです。

つまりは、オブジェクトをboolとして扱う場合に色々できちゃいけないものとかあるから、それをできないようにしようということです。
で、本題はこのイディオムの意味じゃなくて、その中で出てきている

class Testable {
  bool ok_;
  typedef void (Testable::*bool_type)() const;
  void this_type_does_not_support_comparisons() const {}
public:
  explicit Testable(bool b=true):ok_(b) {}

  operator bool_type() const {
    return ok_==true ?
      &Testable::this_type_does_not_support_comparisons : 0;
  }
};

この「operator bool_type() const...」というのがよくわかりませんでした。operator boolやないのか?と。

というわけで実験してみました。

#include <iostream>

// operator boolを定義
class Bool1 {
 public:
  operator bool() {
    return true;
  }
};

// 件のoperator void*()()を定義
class Bool2 {
  typedef void (Bool2::*test)();
 public:
  operator test() {
    return false;
  }
};

// 両方定義してみる。
class Bool3 {
  typedef void (Bool3::*test)();
 public:
  operator test() {
    return false;
  }

  operator bool() {
    return true;
  }
};

int main(int argc, char *argv[]) {
  Bool1 b1;
  Bool2 b2;
  Bool3 b3;

  if (b1) {
    std::cout << "b1 is true" << std::endl;
  }

  if (!b1) {
    std::cout << "b1 is false" << std::endl;
  }

  if (b2) {
    std::cout << "b2 is true" << std::endl;
  }

  if (!b2) {
    std::cout << "b2 is false" << std::endl;
  }

  if (b3) {
    std::cout << "b3 is true" << std::endl;
  }

  if (!b3) {
    std::cout << "b3 is false" << std::endl;
  }
  
  return 0;
}

結果
b1 is true
b2 is false
b3 is true

というわけなので、

  • operator boolが定義されており、boolが必要な場面では、operator boolが優先される
  • operator void(*)()()でも、ちゃんとboolが必要な場面で使える

ということがわかりました。

さて、一番最初に戻りますが、一番最初の例では、operator bool_type()の返り値に、内部関数のアドレスと、0を返しています。

これも試してみました。

#include <iostream>

class Bool4 {
  typedef void (Bool4::*test)();
  void test_not() {}
 public:
  operator test() {
    return &Bool4::test_not;
  }
};

class Bool5 {
  typedef void (Bool5::*test)();
  void test_not() {}
 public:
  operator test() {
    return 0;
  }
};

int main(int argc, char *argv[]) {
  Bool4 b4;
  Bool5 b5;

  if (b4) {
    std::cout << "b4 is true" << std::endl;
  }

  if (static_cast<bool>(b4) == true) {
    std::cout << "b4 is true" << std::endl;
  }

  if (!b4) {
    std::cout << "b4 is false" << std::endl;
  }

  if (b5) {
    std::cout << "b5 is true" << std::endl;
  }

  if (static_cast<bool>(b5) == false) {
    std::cout << "b5 is false" << std::endl;
  }

  if (!b5) {
    std::cout << "b5 is false" << std::endl;
  }
  
  return 0;
}

結果:

b4 is true
b4 is true
b5 is false
b5 is false

static_castをしているのは、無理矢理boolに変換することで、operator 〜を実行するためです。
とりあえずこの実験から、

  • 関数のアドレスを返したらtrue
  • 0を返したらfalse

となりました。よく考えなくても、関数にもアドレスがあるし、0とfalseは同一(だっけ?)なので、0が返されたらfalseになりますね。

ちなみに、void(*)()のoperator overloadは、operator boolに変換されるとのことです。(多分違うけどニュアンスは合ってるはず)

まだまだC++は奥深いです。深すぎて底が見えません。