C++11で導入されたrange-based for、使ってみました。
C++11で導入されたrange-based forは、要するにbegin()とend()だけ定義してあれば、あとは
for(auto sq : Square)
のように書けるらしいという噂を聞きつけ、定義してみた。
Square begin(Square x) { return SQ_ZERO;}
Square end(Square x) { return SQ_NB;}
コンパイルが通らない。
for(auto sq : Square() )
こうか。
コンパイラはoperator *()が定義されていないとか言ってやがる。よくはわからんが、次のように展開されているということなのかな。
1 2 3 4 5 6 |
auto r = Square(); for(auto it = begin(r) ; it != end(r) ; ++ it) { auto sq = *it; ... } |
よろしい
Square operator*(Square sq) { return sq; }
こうだ。よし、コンパイルが通った。他のenumもまとめて登録したいのでマクロにしよう。
1 2 3 4 5 6 7 8 9 10 11 |
// enumに対してrange forで回せるようにするためのhack #define ENABLE_RANGE_OPERATORS_ON(X,ZERO,NB) \ inline X operator*(X x) { return x; } \ inline X begin(X x) { return ZERO; } \ inline X end(X x) { return NB; } ENABLE_RANGE_OPERATORS_ON(Square,SQ_ZERO,SQ_NB) ENABLE_RANGE_OPERATORS_ON(Color, COLOR_ZERO, COLOR_NB) ENABLE_RANGE_OPERATORS_ON(File,FILE_ZERO,FILE_NB) ENABLE_RANGE_OPERATORS_ON(Rank,RANK_ZERO,RANK_NB) ENABLE_RANGE_OPERATORS_ON(Piece, NO_PIECE, PIECE_NB) |
よし、定義が終わった。
for(auto sq : Square() )
ダサいな、これ。「Square()」だか「Square(笑)」だか知らんが、こんなところに()を書きたくないんだよ。
#define SQ Square()
#define COLOR Color()
#define FILE File()
#define RANK Rank()
#define PIECE Piece()
こうするか。
for(auto sq : SQ )
ちょっとマシになった気がする。
上のENABLE_RANGE_OPERATORS_ONのマクロでついでに
#define SQ Square()
みたいなものは定義して欲しいぞ。
1 2 3 4 5 |
#define ENABLE_RANGE_OPERATORS_ON(X,ZERO,NB,XS) \ inline X operator*(X x) { return x; } \ inline X begin(X x) { return ZERO; } \ inline X end(X x) { return NB; } #define XS X() |
うわ。コンパイルが通らない。そうか。C/C++のdefineは1 PASSなのか。いつの時代のプリプロセッサ、いまだに使ってんだよ。本当、何とかしてくれよ。
ともかく、enumに対してrange based forで回せるところまでは来た。これによる速度低下があるのかどうかは俺にはわからん。誰か試して、コメントが欲しい。
次にBitboardだ。こいつも
while(target)
{
auto sq = target.pop();
…
}
みたいなコードは書きあきた。
これからの時代、
for(auto sq : target)
と書きたい。
ええと、begin()とend()があればいいんだよな。beginは初期状態で、endはこれが空のbitboardになればいいから、bitboardの内部状態をiteratorだとみなすとしたら、まずこうだろ。
inline const Bitboard begin(const Bitboard& b) { return b; }
inline const Bitboard end(const Bitboard& b) { return ZERO_BB; }
operator*()がないとか言ってやがる。じゃあ、Bitboard classのなかに入れてやるか。
Square operator*() { return pop(); }
あと、operator !=()と++()がないとか言ってやがる。!=()はBitboardの比較ができればいいからBitboardクラスのをそのまま使えばいいわな。++()は知らん。こうだ。
void operator++() {}
おお、コンパイルが通った。
1 2 3 4 5 |
for(auto sq : goldEffect(BLACK,SQ_49)) cout << sq << ' '; > user 3h 3i 4h 5h 5i |
おお。ちゃんと先手の49金の移動できる升が表示された。面白い。
しかしよく考えてみると次のように内部的に変換されるわけよな。
for(auto it = goldEffect(BLACK,SQ_49); it!=ZERO_BB ; ++it)
{
auto sq = *it; // ここでpopされる。
}
++itのところは空だから問題ないとして、it!=ZERO_BBの比較、少し無駄なような気はする。Bitboardがゼロかどうかを判定するのは、こんな他のBitboard(ZERO_BB)と比較しなくとも、内部的に保持しているSSEレジスタの値がゼロであるかを調べるほうが速いはずで、要するに、ここはend()と比較するように展開して欲しいわけではなく、itがendであるかどうかは自分で書かせて欲しいわけだ。
つまりrange based forは次のように展開して欲しい。
for(auto it = begin(); !it.is_ended() ; ++it)
{
auto sq = *it;
}
operator!=()を
1 2 3 4 |
bool operator == (const Bitboard& rhs) const { return (_mm_testc_si128(_mm_cmpeq_epi8(this->m, rhs.m), _mm_set1_epi8(static_cast } bool operator != (const Bitboard& rhs) const { return !(*this == rhs); } |
こうなっている定義を右辺値見ないようにこう変えてしまえばいいのかも知れないが。
bool operator != (const Bitboard& rhs) const { return this; }
これだと他のところでBitboard同士の比較が出来なくなってしまうしな…。
ちなみにBitboardからboolへの変換子は次のようにしてある。
operator bool() const { return !(_mm_testz_si128(m, _mm_set1_epi8(static_cast<char>(0xffu)))); }
実際のところ、Bitboardに関してはZERO_BBと比較したところで速度低下はほとんどないかも知れないが…よくわからないので誰か実験してコメントください。
それにしても、悩ましい。なんでrange based forにend()みたいなものが要るのだ…。これ設計した奴は、何故、必ずend()が事前に得られると思っているのだ。
while(b)
{
auto sq = b.pop();
…
}
これをforで書けるようにしたいだけなんだよ。なんで20年前からdefineマクロで出来ることがいまだにforで書けないんだよ。C++なんとか委員会はこの20年間、何やってたんだよ。せめて次のように展開して欲しかったなぁ…。これなら、end()みたいなものは必要ないしな。
1 2 3 4 |
for(auto it=begin(b) ; is_ended(it) ; ++it) { auto sq = *it; } |
よしよし。これくらい書いておけば、C++のエキスパートが「それ、XXXで出来るよ」とか速攻でコメントをくれるだろう。はやく来て!C++の偉い人!!
追記
これ単に std::iterator と違うパターンの列挙だから range based for に向かないだけでは reading range-based forはコンピューター将棋で使えるのか? | やねうら王 公式サイト https://t.co/ne69DWKERx
— Akihiko Koizuka (@koizuka) December 18, 2015
偉い人に助けてもらおうと思ったら、本当にどえらいところから返信もらってしまった…。
— やねうら王 (@yaneuraou) December 18, 2015
追記
追加で記事を書きました。
→ 「やねうらおが本当に必要だったもの」は必要ではない
あんまり火種は注ぎたくないので穏便にお願いしたいのですけど、江添さんが代替え案をブログに書いておいででした。
http://cpplover.blogspot.jp/2015/12/blog-post.html
自分も整数を展開するためにわざわざクラス書いたことありますけど、毎度それをどっかから引っ張ってくるのは手間なのであきらめました。
For文自体は大好きなのでもうちょっと手軽になんとか使いたいところです。
そのコメント、リンクが向こうのサイトのトップページになっていたので個別ページのリンクに修正しました。
あ、すいません。うっかりしてました。
向こうにも書いたのですけど、
http://ideone.com/C6TCiG
みたいなコードが標準に入ってほしいと思います。
このコードそのままだと不具合ありそうなので議論はしてほしいですが。
部分範囲型、Pascalには普通ありますよね。30年以上前から…。