2007-02-18

about any_iteretor

 any_iteratorというものがある。これは文字通り、どんなイテレータでも代入できる、イテレータである。たとえば、下のように。
int main() { // intにデリファレンスできるイテレータ typedef IteratorTypeErasure::any_iterator< int , boost::bidirectional_traversal_tag , int & , ptrdiff_t > any_bid_iter ; any_bid_iter iter, end ; std::vector<int> v ; std::list<int> l ; std::set<int> s ; for ( int i = 0 ; i != 10 ; ++i ) { v.push_back(i) ; l.push_back(i) ; s.insert(i) ; } // vectorのイテレータを入れる iter = v.begin() ; end = v.end() ; for ( ; iter != end ; ++iter ) { std::cout << *iter << std::endl ; } // 同じオブジェクトに、listのイテレータも代入可能 iter = l.begin() ; end = l.end() ; for ( ; iter != end ; ++iter ) { std::cout << *iter << std::endl ; } // setも入れてみよう。 iter = s.begin() ; end = s.end() ; for ( ; iter != end ; ++iter ) { std::cout << *iter << std::endl ; } }
 ご覧の通りである。any_iteratorを用いれば、実行時に型を変更できる。つまり、「吾輩は君に、forward iterator以上で、intにデリファレンス可能なイテレータを作ってもらいたい。型については、吾輩が知ったことではない。君の好きにし給え。ただ吾輩の要求通りのイテレータとして振舞えば、それで事は足りる。実行時に、吾輩の担当している部分に、別の型を渡すこともできる。」ということもできる。  そして、私はドキュメントを最後まで読まずに遊び始め、次のようなコードで引っかかった。
// ところで、typedef templateってC++0xに入るの? // こういうところで非常にほしい。 template < typename T > void print(IteratorTypeErasure::any_iterator< T , boost::bidirectional_traversal_tag , T const & , ptrdiff_t > iter , IteratorTypeErasure::any_iterator< T , boost::bidirectional_traversal_tag , T const & , ptrdiff_t > last) { for ( ; iter != last ; ++iter) { std::cout << *iter << std::endl ; } } int main() { typedef IteratorTypeErasure::any_iterator< int , boost::bidirectional_traversal_tag , int & , ptrdiff_t > any_bid_iter ; typedef IteratorTypeErasure::any_iterator< int , boost::bidirectional_traversal_tag , int const & , ptrdiff_t > any_const_bid_iter ; any_bid_iter iter, end ; std::vector<int> v(0) ; for ( int i = 0 ; i != 10 ; ++i) { v.push_back(i) ; } // VC8でコンパイルできない。なぜ。 //print<int>(v.begin(), v.end()) ; // こっちはOK iter = v.begin() ; end = v.end() ; print<int>(iter, end) ; }
 これはなぜかというと、any_iteratorのコンストラクタがexplicitなためだ。ドキュメントにもちゃんと書いてあるが、ドキュメントを最後まで読まないのがこの私だ。  なぜ、explicitなのか。こういう風に使いたい人は、私以外にもいるはずだ。こうすれば、関数に実行時にいろんな型を渡せるはず。何故これがダメなのかというと、メタプログラミングの限界らしい。私はメタプログラミングを呼吸しているわけではないので、完全に理解していないのだが、どうも、SFINAEの規格では、イテレータではない型が渡されたときに、恐ろしく奇妙なコンパイルエラーになってしまうんだそうだ。たとえば、
boost::is_convertible< int, some_any_iterator_type >
というメタファンクションを書いた場合、答えを得られずに、単なるコンパイルエラーになってしまうらしい。これはまずいというわけだ。あとは、プロクシクラスを使った場合の問題など。  むちゃくちゃなコンパイルエラーを出すより、無効にしたほうがいい?  おまけの超邪悪なコード
int main() { // うひひひひ typedef IteratorTypeErasure::any_iterator< boost::any , boost::bidirectional_traversal_tag , boost::any & , ptrdiff_t > Iter ; Iter iter, end ; std::vector<boost::any> v ; // ふはは for (int i = 0 ; i != 20 ; ++i) { switch(rand()%3) { case 0 : v.push_back(i) ; break ; case 1 : v.push_back(float(i)) ; break ; case 2 : v.push_back(boost::lexical_cast<std::string>(i)) ; break ; } } iter = v.begin() ; end = v.end() ; // へっへっへ for ( ; iter != end ; ++iter) { if (iter->type() == typeid(int)) { std::cout << "int : " << boost::any_cast<int>(*iter) << std::endl ; } else if (iter->type() == typeid(float) ) { std::cout << "float : " << boost::any_cast<float>(*iter) << std::endl ; } else if (iter->type() == typeid(std::string)) { std::cout << "string : " << boost::any_cast<std::string>(*iter) << std::endl ; } } }
参考:http://d.hatena.ne.jp/mb2sync/20070217#p1

2 comments:

MB said...

iteratorはいろんな文脈でis_convertibleが呼ばれるので
どうしてもimplicitにできないのだと思います
その点ではany_rangeでは問題ないです(コピーできることが求められないので)
実は、any_iteratorなんてアンチパターンだよ、とかと思ってたんですが、
再帰rangeになるとどうしても必要なので追いかけてます

江添亮 said...

なるほど、なぜis_convertibleなのかと思ったら、そういうことだったのですか。
たしかにコンパイルエラーになるのは困りますね。

それにしても、こういうコードを書ける人は、人間コンパイラなのでしょうか。とても私には書けそうにない。