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
iteratorはいろんな文脈でis_convertibleが呼ばれるので
ReplyDeleteどうしてもimplicitにできないのだと思います
その点ではany_rangeでは問題ないです(コピーできることが求められないので)
実は、any_iteratorなんてアンチパターンだよ、とかと思ってたんですが、
再帰rangeになるとどうしても必要なので追いかけてます
なるほど、なぜis_convertibleなのかと思ったら、そういうことだったのですか。
ReplyDeleteたしかにコンパイルエラーになるのは困りますね。
それにしても、こういうコードを書ける人は、人間コンパイラなのでしょうか。とても私には書けそうにない。