江添亮
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キャプチャーには問題が二つ
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
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の文法と機能