2013-05-28

C++14にやってくる見た目に分かりやすい新機能

C++14のCDも公開され、いよいよC++14も形になってきた。いま書いている本はC++11だが、C++14はGCCとClangといった二大C++コンパイラーで数年のうちに実装されるだろうから、もうすぐ実際に使うことができる。

今回は、詳細な説明は省いて、サンプルコードで新機能を語りたいと思う。以下の新機能は、すでにドラフト入りしており、正式採用はまず間違いない機能である。ちなみに、コンパイルしていないので正しいかどうか検証していない。

2進数リテラル

int bi = 0b11110000 ; // 10進数リテラルでは240

2進数の記述が直感的になる。

自動ストレージ上に確保される動的な長さの配列

void f( std::size_t size )
{
    int buf[size] ; // OK
}

もちろん、クラスの配列も可能だし、コンストラクターやデストラクターも正しく呼ばれる。配列は原始的で範囲外アクセスもチェックしてくれないが、ライブラリによる実装よりも直感的だろう。

汎用ラムダキャプチャー

class X
{
private :
    int member ;

public :

    std::function< int (void) > get_closure() const
    {
        // 非staticデータメンバーをコピーキャプチャー
        return [ member = member ] { return member ; } ;
    }
} ;

int main()
{
    std::function< int (void) > func ;

    {
        X x ;
        func = x.get_closure() ;
    } // xは破棄されている

    func() ; // OK、memberはコピーキャプチャーされている

}

今までは、非staticデータメンバーは個別にキャプチャーする方法がなかった。非staticデータメンバーはthisポインターを経由してアクセスされるため、暗黙にリファレンスキャプチャーされてしまっていた。もしコピーキャプチャーしたければ、明示的にローカル変数を宣言するしかなかったのだ。

std::function< int (void) > get_closure() const
{
    int member = this->member ;
    return [ member ] { return member ; } ;
}

これは面倒だし、非効率的だ。C++14では汎用的なラムダキャプチャー(初期化キャプチャー)が追加されたおかげで、キャプチャーする識別子をどのように初期化するか記述できる。

void f()
{
    int x ;

    [ x = x ] { } ; // xをxとしてコピーキャプチャー
    [ x = x + 1 ] { } ; // xをxとしてコピーキャプチャー、ただし初期化はx+1
    [ y = x ] { } ; // xをyとしてコピーキャプチャー

    [ &x = x ] { } // xをxとしてリファレンスキャプチャー

    [ x = std::move(x) ] { } ; // xをxとしてコピーキャプチャー、ただしムーブコンストラクターが呼ばれる
}

これにより、ムーブしてキャプチャーも可能になる。ムーブによるキャプチャーではなく、ムーブしてキャプチャーなのに注意。

変数テンプレート

// 変数テンプレート
template < typename T >
constexpr T pi = static_cast<T>(3.1415926535) ;

// 明示的特殊化
template < >
constexpr int pi = 3 ;

変数をテンプレート宣言できるように変更。これは新機能ではあるが、文面上の変更は極めて小さい。単に変数はテンプレート宣言できないという従来の制限を緩和するだけだからだ。

変数テンプレートは、上記のように定数の実装に使える。これは従来の関数テンプレートでもできたが、pi<double>()のかわりに、pi<double>と記述できるのが大きい。どうせ引数を取らないのならば、()は冗長だ。

普通の関数でも戻り値の型の推定

// C++14
template < typename T, typename U >
auto f( T const & t, U const & u )
{
    return t + u ;
}

// C++11
template < typename T, typename U >
auto f( T const & t, U const & u ) -> decltype( t + u )
{
    return t + u ;
}

lambda式でできる関数の戻り値の型の推定を、普通の関数にも導入。これによる冗長なコードが省ける。

ジェネリックlambda式

template < typename Func >
void f( Func func )
{
    func( 0 ) ; // int
    func( 0.0 ) ; // double
    func( "hello" ) ; // char const *
}

int main()
{
    f( []( auto x ) { std::cout << x << std::endl ; } ) ;
}

クラスによる関数オブジェクトならば、メンバーテンプレートで実現できていたことを、lambda式でも実現可能にする。

constexpr関数の制限緩和

// C++14版のconstexpr関数による平方根の計算
constexpr double sqrt( double s )
{
    double x = s / 2.0 ;
    double prev = 0.0 ;

    while ( x != prev )
    {
        prev = x ;
        x = (x + s / x ) / 2.0 ;
    }

    return x ;
}


// C++11版のconstexpr関数による平方根の計算
constexpr double sqrt_impl( double s, double x, double prev )
{
    return x != prev ? sqrt_impl( s, ( x + s / x ) / 2.0, x ) : x ;
}

constexpr double sqrt( double s )
{
    return sqrt_impl( s, s/2.0, s ) ;

}

constexpr関数で、ローカル変数の宣言、ローカル変数の変更、ループ文(for, while, do-while)の使用、条件文(if, swtich)の使用を許可する変更。これにより、constexpr関数の記述がまともになる。

さらに詳しい全変更内容を知りたい人は、Editor's Reportを参照。

C++ Editor's Report, May 2013

4 comments:

Anonymous said...

2進法以外は使えねーし使わねー

Anonymous said...

そんなしょうもない機能より、自動委譲をさっさと搭載して欲しい。Smalltalkも、Goも、Pythonも、Rubyですら手で書かずとも自動で委譲できる機能があるのに不便すぎる。

http://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2002/n1363.htm

江添亮 said...

ちょっと違うんですが、Opaque Typedefというのが提案されています。Strong Typedefという名前でも知られています。

N3741

この機能があれば、自動移譲に少し近いわずらわしさを省いた記述が実現できます。
ただし、あくまで型の別名であり、派生関係にはなりませんが。

Anonymous said...

Strong Typedefじゃ目的がだいぶ違うでしょう。
自動委譲でやりたいことは、Wikipediaの委譲記事でいうSmalltalkやGoの例を簡単に記述することです。
https://ja.wikipedia.org/wiki/%E5%A7%94%E8%AD%B2

SimpleContext *simple_context = device.Context();
ComplexContext *complex_context = new ComplexContextForSimple( simple_context );

//SimpleContextのDraw( const MoveTo& )が呼ばれる
complex_context->Draw( MoveTo( 10, 20 ) );

//SimpleContextのDraw( const LineTo& )が呼ばれる
complex_context->Draw( LineTo( 10, 20 ) );

//ComplexContextForSimpleのDraw( const CurveTo& )が呼ばれる
complex_context->Draw( CurveTo( 0, 20, 50, 50 ) );

上記の例をComplexContextForSimpleにDraw( const MoveTo& )や
Draw( const LineTo& )を書かずに済むようにならないと代わりにはなりません。

ちなみに、自動委譲の議論は海外のサイトだとそれなりにある上に提案まででてたのに
なんで採用されないんでしょうね。それに実装もそんなに難しくないでしょうに。
例えば、下記の仕様なら

class ComplexContextForSimple:public ComplexContext
{
  /*
  ComplexContextForSimpleにもComplexContextも無い関数が呼ばれると
  contextのメンバー関数が->演算子経由で呼ばれる
  */
  [delegate] SimpleContext *context;
public:
  ComplexContextForSimple( SimpleContext *client_context ):
    context( client_context )
  {
  }

  void Draw( const CurveTo & )
  {
    //・・・
  }
};

こう展開すれば済むだけの話でしょうに
class ComplexContextForSimple:public ComplexContext
{
  SimpleContext *context;
public:
  ComplexContextForSimple( SimpleContext *client_context ):
    context( client_context )
  {
  }

  void Draw( const CurveTo & )
  {
    //・・・
  }

  void Draw( const MoveTo &argument0 )
  {
    context->Draw( argument0 );
  }

  void Draw( const LineTo &argument0 )
  {
    context->Draw( argument0 );
  }
};