On Github EzoeRyou / kabukiza-tech2-slide
江添亮http://cpplover.blogspot.jp/ boostcpp@gmail.com@EzoeRyou
GFDL 1.3 with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
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をサポートしない
void f( std::size_t size ) { struct { // GCCの独自拡張VLAIS // C++14ではエラー char buf[size] ; } vlais ; }
C++14は実行時サイズ配列
C99は可変長配列
違いがある
たとえば、sizeofはサポートしない
みんな配列を使いたがる
ライブラリベースの実装では納得しない
C++14では、ライブラリによる同等機能のdynarrayも入る
詳細は論文N3662などで熟知すべし
#include <dynarray> void f( std::size_t size ) { std::dynarray<char> a(size) ; }
(σ・∀・)σ gets() !
ダンディ坂野
Never use gets().
man 3 gets(GNU libc)
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では、関数の戻り値の型を省略すると、暗黙にint型になった
f() { return 0.0 ; } // int
C++14の戻り値の型推定機能は、小汚いK&R Cとは違う
関数本体の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)というものがある
時間がないので省略
詳細は論文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 ;
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のパラメーターのタイプをパラメタライズドする
泥臭い説明
クロージャーオブジェクトの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式に与える
クロージャーオブジェクトの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キャプチャーに任意の初期化子を書く機能
原理はクロージャーオブジェクトのデータメンバーへの初期化子
名前通り、汎用的に使えるキャプチャー
#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データメンバーがコピーキャプチャできない ムーブキャプチャーが存在しない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 ; } } ;
非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キャプチャー
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なコードと、 まだconstexprでないコードしか存在しない。
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
constexpr関数の制限を大幅に緩和する変更
制限が多い
制限を緩和
数値Sの平方根の計算方法
Babylonian method
適当な初期値X0を取る(平方根に近い値が望ましい) Xn+1をXnとS/Xnの平均とする(算術平均を用いてよい) 十分な精度が得られるまで、ステップ2を繰り返す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 ; }
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 ) ; }
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で販売中
C++11の参考書をGitHubで公開中
GitHub: EzoeRyou/cpp-book
GitHub Pages: C++11の文法と機能