2011-01-17

C++0xの新機能によってユーザーが受ける恩恵

どうも執筆がはかどらないので、気分転換に、C++0xの新機能によって、一般ユーザーのコードがどのように便利になるかを示してみようと思う。

問題:以下のような仕様の関数printを実装し、また実際に呼び出しなさい。
宣言:関数printは、std::vector< std::string > const &を仮引数に取り、std::vector< std::string >::const_iteratorを戻り値の型として返す。
本体:関数printは、仮引数のvectorの要素をすべて標準出力に出力する。
戻り値:v.cbegin()。

C++03の範囲(ただし、cbegin、cendあり)でこれを実装すると、以下のようになる。

std::vector< std::string >::const_iterator
print( std::vector< std::string > const & v ) 
{
    for (   std::vector< std::string >::const_iterator iter = v.cbegin(), end = v.cend() ;
            iter != end ;
            ++iter )
    {
        std::cout << *iter << std::endl ;
    }

    return v.cbegin() ;
}


int main ()
{
    std::vector< std::string > v ;
    v.push_back("foo") ;
    v.push_back("bar") ;
    v.push_back("hoge") ;
    v.push_back("moke") ;

    print( v ) ;
}

このコードは、何をしているのかさっぱりわからない。おそらく、一ヶ月後にこのコードを見なおしても、書いた本人ですら、まともに読めないだろう。C++0xの新機能を使って、このコードを読めるようにしてみよう。

まず問題なのは、std::vector< std::string >::const_iteratorなどという、べらぼうに長い型名だ。これを何とかしよう。それには、auto specifierとdecltypeが使える。

auto specifierは、変数の型を、初期化子から決定してくれる、大変便利な新機能である。decltypeは、型をdecltypeに渡した未評価式の型にしてくれる、これまた便利な新機能である。

// 新しい関数記法
auto print( std::vector< std::string > const & v )
    -> decltype( v.cbegin() ) // decltype
{
// auto
    for (   auto iter = v.cbegin(), end = v.cend() ;
            iter != end ;
            ++iter )
    {
        std::cout << *iter << std::endl ;
    }

    return v.cbegin() ;
}

だいぶマシになった。次は、for文だ。わざわざループのためのイテレーターのインクリメントや、終了条件の判定を、自分で書くのは面倒だし、間違いのもとだ。これには、range-based forが使える。

auto print( std::vector< std::string > const & v ) -> decltype( v.cbegin() )
{
    for ( auto elem : v )
    {
        std::cout << elem << std::endl ;
    }

    return v.cbegin() ;
}

これで、関数printは、実に短く簡潔に書ける。これで、一年後にコードを読んでも、スラスラと読めるだろう。

ところで、このprint関数を呼び出すために、vectorを用意するのが、これまた面倒だ。

    std::vector< std::string > v ;
    v.push_back("foo") ;
    v.push_back("bar") ;
    v.push_back("hoge") ;
    v.push_back("moke") ;

これには、initializer listが使える。

int main ()
{
    std::vector< std::string > v = { "foo", "bar", "hoge", "moke" } ;
    print( v ) ;
}

これも、POD型のクラスの初期化と同じで、非常に分かりやすい。

2 comments:

ぺろぺろくんかくんか said...

流石に

 print( std::vector< std::string >( "foo", "bar", "hoge", "moke" ) ) ;

とか

 print( std::vector< std::string >{ "foo", "bar", "hoge", "moke" } ) ;


的なことは出来ないですよね?

江添亮 said...

後者はできます。

仮引数の型が、std::vector< std::string >、もしくはそのconst lvalueリファレンスかrvalueリファレンス型だとして、


print( std::vector< std::string >{ "foo", "bar", "hoge", "moke" } ) ;

や、


print( std::vector< std::string >({ "foo", "bar", "hoge", "moke" }) ) ;

はできます。