2015-12-18

やねうらおが本当に必要だったもの

range-based forはコンピューター将棋で使えるのか? | やねうら王 公式サイト

やねうらおが、range-based forが使えないとこぼしている。しかしその利用例をみるに、そもそもrange-based forを使うべきではない。

range-based forは、イテレーターというコンセプト(まだコンセプト機能はC++にないが)に従っている。イテレーターはポインター操作をモデルとしている。

  • イテレーターは要素群の中のある要素を指していて、operator *で要素を参照できる。
  • イテレーターの指す要素は、operator ++で次の要素に移動できる。
  • イテレーターはhalf-openとなっていて、要素群の最後の要素のひとつ次の、何も指していない状態のイテレーターが存在する。これを番兵のように使い要素の端に到達したことを判定する

一方、やねうらおのコードは、本来イテレーターではないものを無理やりイテレーターのようにしている。Squareというのは、おそらくunscoped enumだ。(C++11ではscoped enumという強い型をもつenumが追加された。従来の弱いenumはunscoped enumと呼ばれる)。つまり整数型と番兵なのだろう。

したがって、やねうらおに必要なのはrange-based forではない。

ところで、記事中でやねうらおはCプリプロセッサーマクロを多用している。これは極めて醜悪だ。C++にはlambda式が存在するので、同等のコードはもっと綺麗に書ける。

おそらく、やねうらおが本当にほしかったのは、こういうものではないのか。

template < typename T, typename Func >
void yaneu_for( T i, T sentinel, Func func )
{
    for ( ; i != sentinel ; ++i )
    {
        func( i ) ;
    }
}


int main()
{
    yaneu_for( ZERO, NB,
        []( auto i )
        {
            // 処理
        } ) ;


    int hoge ;

    yaneu_for( ZERO, NB,
        [&]( auto i )
        {
            // lambda式の外の変数も使える
            hoge = i ;
        } ) ;
}

初期値iと番兵sentinelを指定すると、sentinelに到達するまでインクリメントしつつ関数オブジェクトfuncを呼ぶ。関数オブジェクトはlambda式を使うとその場に書ける。

ところで、range-based for文は、以下のように使う。

for ( for-range-declaration : range )
    statement

これは、以下のように展開される(多少の細部を省略している)

{ // 新しいブロックスコープ

    auto && __range = range ;

    for (   auto __begin = begin-expr,
                 __end = end-expr ;
            __begin != end ;
            ++__begin ) {
        for-range-declaration = *__begin() ;
        statement
     }
}

begin-exprとend-exprは、decltype(__range)の型次第で変わるが、いずれもbegin()/end()に相当する処理である。

具体的な例をしめす。以下のようなコードは、

std::vector<int> v{ 1, 2, 3 } ;
for ( auto i : v )
{
    std::cout << i << std::endl ;
}

以下のように展開される。

std::vector<int> v{ 1, 2, 3 } ;
{
    auto && __range = v ;

    for ( auto __begin = v.begin(), __end = v.end() ;
            __begin != __end ;
            ++__begin )
    {
        auto i = *__begin ;
        std::cout << i << std::endl ;
    }

}

v.end()は一度しか呼ばれない。

以下のような場合、

int a[100] ;
for ( int & i : a )
{
    i = 0 ;   
}

以下のように展開される。

int a[100] ;
{
    auto && __range = a ;
    for (   __begin = a, __end = a + 100 ;
            __begin != end ;
            ++__begin ; )
    {
        int & i = *__begin ;
        i = 0 ;
    }
}

それから、もはや現代でinlineを使う理由が思いつかない。

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

3 comments:

Anonymous said...

レンジベースドフォー便利ですよね。
便利すぎてもうちょっと拡張できないかよく考えます。
個人的には、
for(auto& o:10){/**/}
って書いたら0->10で展開してほしいのですけど、
なんかすげー面倒なクラス書かないと実現しないのですよね。
もしくはコンテナ初期化して使うか。それも無駄が多いですし。
標準でなんらかのカバークラス出してくれないか待ってマース。

Anonymous said...

やねうって完全にオワコンだしクソみたいなコードしか書けないからしゃーない

Anonymous said...

ttp://ideone.com/C6TCiG
こんな感じのコードが標準にマージされてほしい人生でした。
逐一、過去のコード漁ってポートしてくるの面倒なんで標準に入れてくれたらなーと、淡い期待をしています。