OSDN Git Service

bool_comparable を最適化
[gintenlib/gintenlib.git] / gintenlib / bool_comparable.hpp
1 #ifndef GINTENLIB_INCLUDED_BOOL_COMPARABLE_HPP_
2 #define GINTENLIB_INCLUDED_BOOL_COMPARABLE_HPP_
3
4 /*
5   bool_comparable : safe_boolイディオム提供
6   
7   宣言:
8     template<typename Derived>
9     class bool_comparable
10     {
11      public:
12       // safe_bool 本体
13       operator unspecified_bool_type() const;
14       // 派生したクラスの側で operator! が定義されていない場合、
15       // 派生したクラスの boolean_test() メンバ関数があればその否定を返す。
16       // operator!() も boolean_test() もなければ、エラー
17       bool operator!() const;
18     
19       // bool との比較。 int のときは反応しないようになってます
20       friend bool operator==( const bool_comparable& lhs, bool rhs );
21       friend bool operator==( bool lhs, const bool_comparable& rhs );
22       friend bool operator!=( const bool_comparable& lhs, bool rhs );
23       friend bool operator!=( bool lhs, const bool_comparable& rhs );
24     
25      protected:
26       bool_comparable() {}
27     
28     };  // class bool_comparable<Derived>
29     
30         
31     // 他、呼び出されるとコンパイルエラーになる operator== と operator!= が宣言されています
32
33   機能:
34     boost::operators みたいな感じに、簡単に safe_bool イディオムを実現します。
35     使用法は、bool_comparable のテンプレート引数に製作するクラスの名前を入れて public 継承。
36     そして製作するクラスに、
37       bool operator!() const;
38     関数か、あるいは
39       bool boolean_test() const;
40     関数のどちらかを定義すればOKです(両方ある場合は operator!() が優先されます)。
41   
42   使用法 : 
43     // 例として boost::optional 的な何かを作りましょう
44     #include <boost/scoped_ptr.hpp>
45     
46     template<typename T>
47     class my_optional
48       : public gintenlib::bool_comparable< my_optional<T> > // このように使う
49     {
50       // 本体部分はなんら気にせず書いてよい
51      public:
52       typedef  T  value_type;
53       
54       typedef       T&       reference;
55       typedef const T& const_reference;
56       
57       my_optional() : p() {}
58       my_optional( const T& x ) : p( new T(x) ) {}
59       
60       reference        operator*()       { return *p; }
61       const_reference  operator*() const { return *p; }
62       
63       // operator!() を確実に実装することを忘れなければ。
64       bool operator!() const
65       {
66         return !p;
67       }
68       // これにより、bool比較も出来るようになる
69       
70       // ちなみに operator!() じゃなくて
71       // bool boolean_test() const { return p.get(); }
72       // これでもいい(その場合、operator! は自動定義)
73       
74       // そのほか、コピーコンストラクタとか代入演算子とかは面倒だから省略
75       
76      private:
77       // 面倒なポインタ管理とかしたくないので scoped_ptr を使う
78       boost::scoped_ptr<T> p;
79       
80     };
81     
82     int main()
83     {
84       my_optional<int> a, b(1), c(0);
85       
86       // if の条件部として
87       if( a )
88       {
89         assert( !"a is empty." );
90       }
91       
92       // && や || と組み合わせて
93       assert( b && c );
94       
95       // ゼロとの比較
96       assert( a == 0 && b != 0 && c != 0 );
97       // assert( b == 1 );  // 1 とは比較できない
98       
99       // bool 値との比較
100       assert( a == false && b == true && c == true );
101       // static_cast
102       assert( static_cast<bool>(b) == static_cast<bool>(c) );
103       // assert( a != b ); // コレはダメ(もし b と c を比較したら?)
104
105   注意事項:
106     ・boost::enable_if を使っています。古いコンパイラでは動きません。
107     ・それが嫌なら GINTENLIB_BOOL_COMPARABLE_NO_ENABLE_IF マクロを定義すればOKです。
108     ・ただし、その場合、上の例での「b == 1」的な表現がコンパイル通ってしまいます。
109     
110     ・「 a == b 」のような、派生先のクラス同士での比較は、デフォルトではコンパイルエラーになります。
111     ・ですが、当然、派生先で operator== を定義しておけば、きちんと比較できるようになります。
112     ・ただし、派生先のクラスが「 0 から暗黙変換可能」であり「operator==が定義されている」場合は、
113       「 x == 0 」のような比較が、ゼロとの比較に限りですが、出来なくなってしまいます:
114         struct convertible_from_int
115           : gintenlib::bool_comparable<convertible_from_int>,
116             private boost::equality_comparable<convertible_from_int>
117         {
118           int value;
119           convertible_from_int( int x = 0 ) : value(x) {} // int から暗黙変換できると・・・
120           
121           bool operator!() const { return value == 0; }
122           
123           // operator== による比較が出来る場合
124           friend bool operator==( const convertible_from_int& lhs, const convertible_from_int& rhs )
125           {
126             return lhs.value == rhs.value;
127           }
128          
129         };  
130         
131         int main()
132         {
133           convertible_from_int x, y = 2;
134           assert( !x ); // こういうのは普通に出来るが
135           assert( y );
136           assert( x == false );
137           assert( y == true );
138           
139           // assert( x == 0 ); // コレが実行できない(曖昧になってしまう)
140           // assert( y != 0 ); // 当然これもダメ
141           
142           assert( y == 2 ); // でもコレはOK
143           assert( x != 1 ); // コレもOK
144         }
145        
146     ・その場合は int との比較を別個定義してやればOKです:
147         struct convertible_from_int
148           : gintenlib::bool_comparable<convertible_from_int>,
149             private boost::equality_comparable<convertible_from_int>,
150             private boost::equality_comparable<convertible_from_int, int>
151         {
152           int value;
153           convertible_from_int( int x = 0 ) : value(x) {} // int から暗黙変換できる場合でも
154           
155           bool boolean_test() const { return value; } // boolean_test を使ってみる。
156           
157           friend bool operator==( const piyo& lhs, const piyo& rhs )
158           {
159             return lhs.value == rhs.value;
160           }
161           // int との比較を明示的に定義さえすれば
162           friend bool operator==( const convertible_from_int& lhs, int rhs )
163           {
164             return lhs.value == rhs;
165           }
166          
167         };
168         
169         int main()
170         {
171           convertible_from_int x, y(2)
172           assert( x == 0 ); // OK
173           assert( y != 0 );
174         }
175     
176 */
177
178 #ifndef GINTENLIB_BOOL_COMPARABLE_NO_ENABLE_IF
179   #include "enable_if.hpp"
180   #include <boost/type_traits/is_same.hpp>
181 #endif
182
183 namespace gintenlib
184 {
185   // ヘルパ構造体
186   namespace detail_
187   {
188     struct bool_comparable_helper
189     {
190       // operator bool_type() の戻り値として使われる値
191       void bool_value() const {}
192       
193      private:
194       // コンパイルエラーを起こすための private 関数
195       bool this_type_does_not_support_comparisons_();
196     
197     };
198   }
199   
200  namespace bool_comparable_ // ADL 回避
201  {
202   // 本体
203   template<typename Derived>
204   class bool_comparable
205   {
206     // safe-bool に使う型(メンバ関数へのポインタ)
207     typedef void (bool_comparable::*bool_type)() const;
208     
209     // 派生したクラスを得る関数
210     const Derived& derived() const { return *static_cast<const Derived*>(this); }
211     
212     // ヘルパ構造体名を適当に短縮
213     typedef ::gintenlib::detail_::bool_comparable_helper helper;
214     
215    public:
216     // 本題
217     operator bool_type() const
218     {
219       return bool_test_() ? bool_value_() : 0;
220       // bool_test_, bool_value_ は後で定義する
221     }
222   
223     // bool との比較
224     
225   #ifndef GINTENLIB_BOOL_COMPARABLE_NO_ENABLE_IF
226     
227     // enable_if で bool と比較する時だけ有効にすることで、x == 1 のような比較を無力化する
228     // todo: g++ 以外のコンパイラでの動作チェック
229     
230     // 実装
231     template<typename U>
232     friend typename enable_if<boost::is_same<U, bool>, bool>::type
233       operator==( const Derived& lhs, U rhs )
234     {
235       return bool(lhs) == rhs;
236     }
237     template<typename U>
238     friend typename enable_if<boost::is_same<U, bool>, bool>::type
239       operator==( U lhs, const Derived& rhs )
240     {
241       return lhs == bool(rhs);
242     }
243     template<typename U>
244     friend typename enable_if<boost::is_same<U, bool>, bool>::type
245       operator!=( const Derived& lhs, U rhs )
246     {
247       return bool(lhs) != rhs;
248     }
249     template<typename U>
250     friend typename enable_if<boost::is_same<U, bool>, bool>::type
251       operator!=( U lhs, const Derived& rhs )
252     {
253       return lhs != bool(rhs);
254     }
255     
256   #else   // #ifndef GINTENLIB_BOOL_COMPARABLE_NO_ENABLE_IF
257     
258     // 一応 enable_if を使わない版も用意
259     // この場合、 x == 1 のような表現が通ってしまうが仕方ないね
260     friend bool operator==( const Derived& lhs, bool rhs )
261     {
262       return bool(lhs) == rhs;
263     }
264     friend bool operator==( bool lhs, const Derived& rhs )
265     {
266       return lhs == bool(rhs);
267     }
268     friend bool operator!=( const Derived& lhs, bool rhs )
269     {
270       return bool(lhs) != rhs;
271     }
272     friend bool operator!=( bool lhs, const Derived& rhs )
273     {
274       return lhs != bool(rhs);
275     }
276     
277   #endif  // #ifndef GINTENLIB_BOOL_COMPARABLE_NO_ENABLE_IF
278   
279    private:
280     // 実装補助
281     // bool_type として返す値
282     static bool_type bool_value_()
283     {
284       // 関数を無駄に作らせない工夫。どうせ bool_type の値は使わないし
285       return reinterpret_cast<bool_type>( &helper::bool_value );
286     }
287     
288     // テスト関数
289     // まず Derived::operator!() を見に行く
290     bool bool_test_() const
291     {
292       return !( !derived() );
293     }
294     
295    public:
296     // Derived 側で operator!() が定義されて無いなら
297     // Derived::boolean_test() 関数がないか探し、無ければコンパイルエラー
298     bool operator!() const
299     {
300       return !( derived().boolean_test() );
301     }
302     // friend 関数版。存在する意味は特に無い。
303     friend bool boolean_test( const bool_comparable& x )
304     {
305       return x.bool_test_();
306     }
307     
308     // 比較はエラーになる( Derived 側で特別に定義すればOK)
309     
310     // 等値比較
311     friend bool operator==( const bool_comparable& lhs, const bool_comparable& rhs )
312     {
313       return helper().this_type_does_not_support_comparisons_();
314     }
315     // 不等値比較
316     friend bool operator!=( const bool_comparable& lhs, const bool_comparable& rhs )
317     {
318       return helper().this_type_does_not_support_comparisons_();
319     }
320   
321    protected:
322     // 派生クラス以外からの構築禁止
323      bool_comparable() {}
324     // ~bool_comparable() {}  // trivial にしたい
325     
326   };  // class bool_comparable<Derived>
327   
328  }  // namespace bool_comparable_
329  
330   // gintenlib 名前空間に引き入れる
331   using namespace bool_comparable_;
332
333 } // namespace gintenlib
334
335 #endif  // #ifndef GINTENLIB_INCLUDED_BOOL_COMPARABLE_HPP_