C++14の新機能 – Themes – Transition Styles



C++14の新機能 – Themes – Transition Styles

1 5


kabukiza-tech2-slide

Presentation slide document for kabukiza.tech#2

On Github EzoeRyou / kabukiza-tech2-slide

C++14の新機能

江添亮http://cpplover.blogspot.jp/ boostcpp@gmail.com@EzoeRyou

GFDL 1.3 with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.

Themes

Sky - Beige - Simple - Serif - Night - Default

Transition Styles

Cube - Page - Concave - Zoom - Linear - Fade - None - Default

注意

これは2013年10月13日に発行されたC++のドラフト規格に基づく

正式に制定され発行されたISO規格ではない

まだ文法や機能は変わりうるものと心得よ

二進数リテラル

ヤレ由良助待兼たはやい

塩冶判官

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

匿名希望さん

二進数リテラル

待ちかねた二進数の整数リテラルがC++に追加

int x = 0b10 ; // 2
int y = 0B10101010 ; // 170
int z = 0b10011100 + 0b1111 - 0b1 ;

整数のリテラル

二進数リテラルは浮動小数点数には使えない

0b1.1 ; // エラー

先例

すでに独自拡張として、GCC, Clang, Digital Mars C++で実装

Java 7、Python、Dでも、同じ文法で提供

下位互換性の問題もない

数値区切り

気にいらん

江添亮

数値区切りとは?

数値リテラルを区切ることができる機能

単一引用符で区切る

区切りの桁は任意

利用例その一

大きな数字を分かりやすくするため区切る

int a = 1'0000 ; // 1万
int b = 1'0000'0000 ; // 1億
long long int c = 1'0000'0000'0000 ; // 1兆

利用例そのニ

8bit単位で区切る

std::uint16_t a = 0b10101010'11110000 ;
std::uint32_t b = 0b11111111'00000000'11111111'00000000 ;
std::uint64_t c = 0xde'ad'be'ef'de'ad'be'ef ;

利用例その三

小数点以下を区切る

double pi = 3.14159'26535'89793'23846 ;

文法

歴史ある数値リテラルを変えるのは難しい

多くの記号が、既存の文法と曖昧になる

実に様々な文法が考慮された

最終的に、区切り文字は単一引用符に決定

望まれた機能

ドラフト入りしたのは2013年9月のシカゴ会議

2013年4月のブリストル会議では却下

CDに入らず

「必要だ。絶対入れろ」と各国支部からの強いNBコメント

さすがに圧力を無視できずに入った

もう少し議論を深めたほうがよかったかも

実行時サイズ配列

'variable length array in structure' extension will never be supported

Clang、GCCの独自拡張VLAISについて

実行時サイズ配列とは

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

規格上は動的ストレージ上に確保されることも許容される

利用例

配列の長さを実行時に指定できる

void f( std::size_t size )
{
    char a[size] ; // OK
}

クラス内では使えない

GCCの独自拡張は、構造体内で動的配列を宣言できる

通称、構造体内での動的配列

Variable Length Array In Structure(VLAIS)

C++14の実行時サイズ配列はVLAISをサポートしない

VLAISの例

void f( std::size_t size )
{
    struct
    {
        // GCCの独自拡張VLAIS
        // C++14ではエラー
        char buf[size] ;
    } vlais ;
}

C99とは違う

C++14は実行時サイズ配列

C99は可変長配列

違いがある

たとえば、sizeofはサポートしない

何故必要なのか

みんな配列を使いたがる

ライブラリベースの実装では納得しない

ライブラリでの実装

C++14では、ライブラリによる同等機能のdynarrayも入る

詳細は論文N3662などで熟知すべし

#include <dynarray>

void f( std::size_t size )
{
    std::dynarray<char> a(size) ;
}

[[deprecated]]

(σ・∀・)σ gets() !

ダンディ坂野

Never use gets().

man 3 gets(GNU libc)

[[deprecated]]とは?

deprecated属性とは、エンティティである名前を非推奨扱いにする属性

これにより、非推奨扱いの名前を非推奨であるとマークできる

実装は、deprecatedな名前が使われた場合、警告メッセージを出せる

使用例

// 非推奨の名前に対し使用
[[deprecated]] char * gets ( char * str );
// コメントも使える
[[deprecated("auto_ptr is deprecated."
             " Use unique_ptr instead.")]]
template < typename X >
class auto_ptr ;

なぜ必要なのか?

一度決めた名前の挙動を変えるのは難しい

たとえその挙動が、悲惨なものであったとしても

#include <cstdio>

int main()
{
    char buf[256] ;
    std::gets( buf ) ; // ダメ。ゼッタイ。
}

なぜ既存の名前の挙動を変えられないのか?

既存の名前の挙動を変えると、互換性が壊れる

現実的な対応

  • まともな挙動をする新しい名前を提供する
  • 互換性のために、しばらくの間、昔の名前と挙動も残す
char * checked_gets( char * str, std::size_t size )
{
    return std::fgets( str, size, std::stdin ) ;
}

しかし、既存のコードはどうする?

非推奨のマーク

昔の非推奨の名前を、非推奨であるとマークする

[[deprecated("gets is deprecated."
             " Use checked_gets instead.")]]
char * gets ( char * str ) ;

これにより、コンパイラーは非推奨の名前が使われた場合、警告できる

先例

[[deprecated]]の同等機能は、既存のC++実装で独自拡張として提供されている

GCC, Clang, MSVC, Embarcadero

どの独自拡張も、単に属性の文法が違うだけで、機能的には変わらない

戻り値の型推定

If the return type is omitted, int is assumed.

The C programming Language(1978) By Brian W. Kernighan and Dennis M. Ritchie.

戻り値の型推定

関数宣言の戻り値の型を、return文のオペランドの式から推定する機能

使い方

新しい関数記法で、戻り値の型を省略するだけ

auto f() { return 0 ; } // int

auto g() ; // 前方宣言できる
auto g() { return 0.0 ; } // double

auto g() -> int ; // エラー、異なる関数の宣言

K&R Cとの違い

K&R Cでは、関数の戻り値の型を省略すると、暗黙にint型になった

f() { return 0.0 ; } // int

C++14の戻り値の型推定機能は、小汚いK&R Cとは違う

関数本体のreturn文のオペランドの式を評価した結果の型になる

return文

型が一致していれば、複数のreturn文があってもよい

auto f( bool b )
{
    if ( b )
        return 1 ; // int
    else
        return 2 ; // これもint
}

再帰

戻り値の型推定は、再帰関数にも、もちろん使える

return文の型さえ一致していればよい

auto ackermann( unsigned m, unsigned n )
{
    if ( m == 0u )
        return n + 1u ;
    else if ( n == 0u )
        return ackermann( m - 1u, 1u ) ;
    else
        return ackermann( m - 1u,
                          ackermann( m, n - 1u ) ) ;
}

何故必要なのか

型名の具体的な記述は、時として、とても面倒になる

特に、テンプレートが絡むと、とてつもなく面倒になる

面倒な型名

template < typename X, typename Y >
auto f ( X x, Y y ) -> decltype( x + y )
{
    return x + y ;
}

同じ記述が重複している

現実のコードでは、重複部分はもっと長く冗長で複雑になる

decltype(auto)

戻り値の型推定のために追加された機能にdecltype(auto)というものがある

時間がないので省略

詳細は論文N3638 などで熟知すべし

変数テンプレート

円周率は定数である

円周率は必要に応じて変更できる

XeroxのFortranマニュアル

円積問題を肯定的に解決した

ゆえに、円周率は3.2などである

インディアナ州議会

変数テンプレートとは

変数宣言をテンプレート宣言できる機能

使い方

// valueの型はテンプレートパラメーターT
template < typename T >
T value ;

value<int> = 0 ;
value<double> = 0.0 ;

何故必要なのか

プログラミングの世界では、定数に名前をつけるのは良い習慣であるとされている

もし、ある定数が複数の型で表現できる場合、どのような名前をつければいいのか

問題

円周率に名前をつけよ

template < typename Radius >
auto calc_circle_area( Radius const & radius )
{
    return pi * radius * radius ;
}

名前piは、どのように定義したらいいのか

Radiusには、任意の数値型や、数値のようにふるまうクラス型が使われる

ひとつの型を使う方法

constexpr double pi = 3.1415 ;
  • あらゆる数値型はdouble型と互換性があるに違いない
  • piの精度を型に応じて変えることができない

複数の型を使う方法

constexpr int pi_i = 3 ;

constexpr float pi_f = 3.1415 ;

constexpr double pi_d = 3.141592 ;

使い分けが面倒

関数テンプレートを使う方法

任意の型に対応できる

問題解決か?

template < typename T >
constexpr T pi( )
{ return static_cast<T>(3.1415) ; }

// 明示的特殊化
// big_realは独自の精度の高い実数クラス型
template < >
big_real pi<big_real>()
{ return big_real("3.14159265358979323846") ; }

関数テンプレートの問題点

冗長な文法

template < typename Radius >
auto calc_circle_area( Radius const & radius )
{
    return pi<Radius>() * radius * radius ;
}

関数のため、関数呼び出し式()が必要

プログラマー != 数学者

「定数は無引数関数で表すことができる」

数学的には正しい

ただし、プログラマーは数学者ではない

プログラマーは冗長な文法を嫌う

解決方法

いっそのこと、変数をテンプレート宣言できるようにしてしまえばいい

template < typename T >
constexpr T pi = static_cast<T>(3.1415) ;

// 明示的特殊化
template < >
big_real pi<big_real>("3.14159265358979323846") ;

解決方法

関数呼び出し式を書かずにすむ

template < typename Radius >
auto calc_circle_area( Radius const & radius )
{
    return pi<Radius> * radius * radius ;
}

応用

値を返すメタ関数にも使える

冗長な::valueを書く必要がない

template < typename T, typename U >
constexpr bool is_same_v = std::is_same<T, U>::value ;

constexpr bool b = is_same_v< int, int > ;

ジェネリックlambda式

惣じて二つ有物を陰陽に取、兄弟に象る

義経千本桜

ジェネリックlambda式とは?

ポリモーフィックlambda式、多相lambda式とも呼ばれる機能

クールな説明

lambdaのパラメーターのタイプをパラメタライズドする

泥臭い説明

クロージャーオブジェクトのoperator ()をテンプレートにする

使い方

仮引数の型名としてautoを書く

int main()
{
    auto print =  []( auto x ) { std::cout << x ; } ;

    print( 0 ) ; // int
    print( 0.0 ) ; // double
    print( "hello" ) ; // char const *
}

原理はテンプレート

何故必要なのか?

以下のPrintに与えるテンプレート実引数を考える

template < typename Print >
void poly( Print print )
{
    print( 0 ) ; // int
    print( 0.0 ) ; // double
    print( "hello" ) ; // char const *
}

異なる型を受け取る必要がある

関数ポインターやlambda式は使えない

関数オブジェクト

operator()がメンバーテンプレートな関数オブジェクトならば使える

struct Printer
{
    template < typename T >
    void operator () ( T const & value ) const
    { std::cout << value ; }
} ;

関数オブジェクトに劣るlambda式

lambda式は関数オブジェクトを楽に書くためにある

関数オブジェクトに機能で劣るとはどういうことだ

lambda式にもテンプレートをよこせ

ジェネリックlambda式

関数オブジェクトの表現力をlambda式に与える

クロージャーオブジェクトのoperator ()をメンバーテンプレートにできる

lambda式の引数の型の引数化

void f()
{
    poly(   []( auto const & value )
            { std::cout << value ;  } ) ;
}

クロージャーオブジェクト

struct closure_object
{
    template < typename T >
    void operator () ( T const & value ) const
    {
        std::cout << value ;
    }
} ;

パラメーターパック

Variadic Templatesのパラメーターパックも使える

int main()
{
    auto f = []( auto ... args ) { } ;

    f() ;
    f( 0 ) ;
    f( 1, 2, 3, 4, 5 ) ;
    f( 3.14, "hello", nullptr ) ;
}

クロージャーオブジェクト

struct closure_object
{
    template < typename ... Types >
    void operator ()( Types ... args )
    { }
} ;

汎用lambdaキャプチャー

御臺親子を出し参らせ幸の管笠荷と。 細引かなぐりふた押明。 荷底に二人を入参らせ。 旅の用意の風呂敷包。

義経千本桜

汎用lambdaキャプチャーとは?

lambdaキャプチャーに任意の初期化子を書く機能

原理はクロージャーオブジェクトのデータメンバーへの初期化子

名前通り、汎用的に使えるキャプチャー

使い方

#include <initializer_list>

int main()
{
    int x = 0 ;
    [ x = x ](){ } ; // xにxとしてコピーキャプチャ
    [ &x = x](){ } ; // xをxとしてリファレンスキャプチャ
    [ y = x ](){ } ; // xをyとしてコピーキャプチャ
    [ x = x + 1 ](){ } ; // 任意の初期化式が使える 
    [ x{x} ](){ } ; // リスト初期化子も使える
}

何故必要なのか?

C++11のlambdaキャプチャーには問題が二つ

非staticデータメンバーがコピーキャプチャできない ムーブキャプチャーが存在しない

非staticデータメンバー

C++11では、非staticデータメンバーはコピーキャプチャできない

struct S
{
    int data = 0 ;
    auto f() const
    // 危険
    { return [=](){ return data ; } ; }
} ;

実際の挙動

C++11では、非staticデータメンバーはコピーキャプチャできない

struct S
{
    int data = 0 ;
    auto f() const
    // 危険
    { return [this](){ return this->data ; } ; }
} ;

問題のある使い方

int main()
{
    std::function< int (void) > func ;
    {
        S s ;
        func = s.f() ;
    } // sの寿命は尽きている
    func() ; // エラー
}

生成されるクロージャーオブジェクト

struct closure_object
{
    S * const this_ptr ;
    closure_object( S * const this_ptr )
        : this_ptr( this_ptr ) { }

    auto operator () const
    { return this_ptr->data ; }
} ;

C++11のラムダキャプチャー

非staticデータメンバーは、thisポインターを経由してアクセスされる

本質的には、リファレンスキャプチャー

ムーブキャプチャー

C++にムーブキャプチャーは存在しない

#include <memory>

auto f( )
{
    std::unique_ptr<int> p = std::make_unique<int>(0) ;
    return [ p ]() { return *p ; } ; // エラー
}

批難殺到

まだC++11がC++0xと呼ばれていた時代、lambda式には批難が殺到した

C++WG日本支部もNBコメントを送った

しかし、ふさわしい文法を検討する時間がないため、解決は見送られた

クロージャーオブジェクトとはなにか

lambda式は、クロージャーオブジェクトを生成する

このlambda式に対応するクロージャーオブジェクトは?

void f()
{
    int value = 0 ;
    auto f = [value]() { return value ; } ;
    int result = f() ;
}

クロージャーオブジェクトの詳細

class closure_object
{
    int value ;
public :
    closure_object( int value ) : value(value) { }
    auto operator () const
    { return value ; }
} ;

キャプチャの本質

キャプチャとは、クロージャーオブジェクトの非staticデータメンバー

では、データメンバーの初期化方法を指定する文法があればよい

その文法が、汎用lambdaキャプチャー

非staticデータメンバーのコピーキャプチャー

struct S
{
    int data = 0 ;
    auto f() const
    // コピーキャプチャ
    { return [ data = data ](){ return data ; } ; }
} ;

ムーブ風コピーキャプチャー

ムーブキャプチャーは存在しない

汎用lambdaキャプチャーは汎用的に使える

#include <memory>

auto f( )
{
    std::unique_ptr<int> p = std::make_unique<int>(0) ;
    return [ p = std::move(p) ]() { return *p ; } ; // OK
}

その他

キャプチャする変数の名前を変えられる

void f()
{
    int very_long_name = 0 ;
    [ s = very_long_name ]() { } ;
}

constexpr関数の制限緩和

constexprは市民の義務だからな

狂える中3女子ボレロ村上/陶芸C++er

世の中にはconstexprなコードと、 まだconstexprでないコードしか存在しない。

狂える中3女子ボレロ村上/陶芸C++er

i  = 0x5f3759df - ( i >> 1 ); // what the fuck?

Quake III Arena: quake3-1.32b/code/game/q_math.c 561行

constexpr関数の制限緩和とは?

constexpr関数の制限を大幅に緩和する変更

C++11のconstexpr関数

制限が多い

  • constexpr関数の本体は、実質return文ひとつ
  • 条件分岐は条件演算子( expr ? expr : expr )
  • ループは再帰
  • 変更したい変数は引数に追い出す

C++14のconstexpr関数

制限を緩和

  • ローカル変数の宣言
  • ローカル変数の変更
  • 条件文(if, switch)
  • 繰り返し文(for, while, do-while)

sqrtをconstexpr関数で書け

数値Sの平方根の計算方法

Babylonian method

適当な初期値X0を取る(平方根に近い値が望ましい) Xn+1をXnとS/Xnの平均とする(算術平均を用いてよい) 十分な精度が得られるまで、ステップ2を繰り返す

C++11の実行時関数による実装

template < typename T >
T sqrt( T s )
{
    T x = s / 2.0 ; // 適当な初期値
    T prev = 0.0 ;

    while ( x != prev )
    { // 十分な精度が得られるまで繰り返す
        prev = x ;
        x = (x + s / x ) / 2.0 ; // ステップ2
    }
    return x ;
}

C++11のconstexpr関数によるsqrt

template < typename T >
constexpr T sqrt_aux( T s, T x, T prev )
{
    return x != prev ? 
        sqrt_aux( s, ( x + s / x ) / 2.0, x ) : x ;
}

template < typename T >
constexpr T sqrt( T s )
{ return sqrt_aux( s, s/2.0, s ) ; }

C++14のconstexpr関数によるsqrt

template < typename T >
constexpr T sqrt( T s )
{
    T x = s / 2.0 ; // 適当な初期値
    T prev = 0.0 ;

    while ( x != prev )
    { // 十分な精度が得られるまで繰り返す
        prev = x ;
        x = (x + s / x ) / 2.0 ; // ステップ2
    }
    return x ;
}

宣伝

C++11の参考書をGumroadで販売中

https://gumroad.com/l/IwMm

C++11の参考書をGitHubで公開中

GitHub: EzoeRyou/cpp-book

GitHub Pages: C++11の文法と機能

参考文献

2013-10-13時点でのドラフト規格 N3797 二進数リテラル N3472 数値区切り N3499, N3781 実行時サイズ配列 N3639, N3662, N3820 [[deprecated]] N3760 戻り値の型推定 N3638 変数テンプレート N3638 ジェネリックlambda式 N3649 汎用lambdaキャプチャー N3648, N3610 constexpr関数の制限緩和 N3652