2014-09-30

C++14の新機能: ジェネリックlambda

C++14に追加された新機能に、ジェネリックlambdaがある。

lambda式は便利だ。lambda式が便利な理由は、簡潔に書けるからだ。しかし、C++11のlambda式は、引数の型を書かなければならないので、十分に簡潔に書くことはできない。

struct very_long_name
{
    int x ;
    double y ;
    std::string z ;
} ;

void sort( std::vector<very_long_name> & v )
{
    std::sort( v.begin(), v.end(),
        []( very_long_name & a, very_long_name & b )
        {   // 複数のデータメンバーを正しく比較する方法を覚えておくと、
            // いつかためになる。
            return std::tie( a.x, a.y, a.z ) < std::tie( b.x, b.y, b.z ) ;
        } ) ;
}

引数の型ぐらい、コンパイラーが何とかしてほしいものである。そこで、C++14には、引数の型を書かずにすむ、ジェネリックlambdaが追加された。

    std::sort( v.begin(), v.end(),
        []( auto & a, auto & b )
        {
            return std::tie( a.x, a.y, a.z ) < std::tie( b.x, b.y, b.z ) ;
        } ) ;

これだけでは、単に引数の型を省略出来るだけに見えるかもしれない。以下の例で、ジェネリックlambdaの素晴らしさがわかるだろう。

template < typename print >
void f( print p )
{
    p( 42 ) ;
    p( 3.14 ) ;
    p( "hello" ) ;
}

int main()
{
    f( []( auto && elem ) { std::cout << elem << '\n' ; } ) ;
}

ジェネリックlambdaは、テンプレートのようにどんな型でも受け付ける。

lambda式は魔法ではなく、クロージャーオブジェクトと呼ばれる、単なる関数オブジェクトを返すシンタックスシュガーに過ぎない。ジェレリックlambda式も同じだ。テンプレートのようにと書いたが、実際、メンバーテンプレートを持つ関数オブジェクトのシンタックスシュガーに過ぎない。例えば、上記のlambda式によって生成されるクロージャーオブジェクトは、以下のようになる。


struct closure_object
{
    template < typename T >
    void operator ( T && elem ) const
    { std::cout << elem << '\n' ; }
} ;

メンバーテンプレートを使えば、printに与える適切な関数オブジェクトを記述することができるが、C++11のlambda式には、これを可能にする文法がなかった。C++14で、ジェネリックlambdaとして追加された。

なおこの機能はGCC 4.9とClang 3.4で実装されている。

Clang - C++1z, C++14, C++11 and C++98 Status

C++1y/C++14 Support in GCC - GNU Project - Free Software Foundation (FSF)

See Also:

本の虫: C++14の新機能: 2進数リテラル

本の虫: C++14の新機能: decltype(auto)

本の虫: C++14の新機能: 関数の戻り値の型推定

本の虫: C++14の新機能: 初期化lambdaキャプチャー

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

次の論文集がでるのは10月半ばになるので、それまではC++の解説記事を書く。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

C++14の新機能: 初期化lambdaキャプチャー

C++14で追加された新機能を解説していくシリーズ第四弾。今回は、初期化lambdaキャプチャーを解説する。これは、提案では、汎用lambdaキャプチャーと呼ばれていたが、どうやら今は、もっとわかりやすい、初期化lambdaキャプチャーとも呼ばれているようだ。

C++14の初期化lambdaキャプチャーについて解説する前に、まず、C++11のlambdaキャプチャーと、その問題点について解説しなければならない。

lambda式は、自動ストレージ上にあるオブジェクトか、thisをキャプチャできる。

void f()
{
    int x = 0 ;

    // コピーキャプチャ
    [=]{ return x ; } ;
    // リファレンスキャプチャ
    [&]{ x = 1 ; } ;
}

lambda式は、クロージャーオブジェクトという未規定の型のオブジェクトを生成する。lambda式がローカル変数をキャプチャするのは、魔法ではなく、単なる関数オブジェクトのシンタックスシュガーに過ぎない。

たとえば、以下のようなlambda式には、

void f()
{
    int x = 0 ;
    [=]{ return x ; } ;
}

以下のようなクロージャーオブジェクトが生成される。

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

以下のようなlambda式には、

void f()
{
    int x = 0 ;
    [&]{ return x ; } ;
}

以下のようなクロージャーオブジェクトが生成される。

class closure_object
{
    int & x ;
public :
    closure_object( int & x )
        x(x) { }
    int operator()
    {
        return x ;
    }
} ;

これをみると、lambda式が魔法でも何でもなく、従来のC++でもできたことの、シンタックスシュガーに過ぎないことが分かるだろう。

では、何が問題なのか。C++11のlambdaキャプチャーには、二つの問題がある。

データメンバーのキャプチャーができない。

C++11のlambda式は、クラスのデータメンバーのキャプチャーができない。と、こう書くと、読者は以下のようなコードが手持ちのコンパイラーでコンパイルでき、その実行結果も意図通りであることを持って反論するかもしれない。

struct X
{
    int data_member = 0 ;

    void f()
    {
        auto l = [=](){ return data_member ; } ; 
        l() ;
    }

} ;

int main()
{
    X x ;
    x.f() ;    
}

なるほど、たしかにこのコードを読むと、lambda式がデータメンバーをキャプチャーしているように見える。しかし、実際にはdata_memberをキャプチャーしているわけではない。lambda式がキャプチャーしているのは、thisである。data_memberはthis経由で使われているに過ぎない。上のlambda式によって生成されるクロージャーオブジェクトは、以下のようになる。

class closure_object
{
    X * this_ptr ;
public :
    closure_object( X * this_ptr )
        : this_ptr(this_ptr) { }

    int operator ()
    {
        return this_ptr->data_member ;
    }
} ;

data_memberはthisポインター経由でアクセスしているだけで、data_memberをキャプチャーしているわけではない。data_memberはthisポインター経由の間接参照をされるため、コピーされていない。したがって、以下のコードは正しく動かない。

struct X
{
    int data = 0 ;
    void get_lambda()
    {   // コピーキャプチャーしているつもり
        return [=](){ return data ; }
    }
} ;

int main()
{
    std::function< int() > f ;

    {
        X x ;
        f = x.get_lambda ;
    }// xは破棄されている

    f() ; // 挙動は未定義
}

データメンバーはthisポインターを経由して間接参照されているため、thisの参照先が破棄された後では、挙動は未定義となる。

したがって、データメンバーをコピーキャプチャーするには、以下のようにマヌケで冗長な記述をしなければならない。

struct X
{
    int data = 0 ;
    void get_lambda()
    {
        int data = this->data ;
        return [=](){ return data ; }
    }
} ;

ムーブキャプチャーができない。

C++11のlambda式では、変数をムーブしてキャプチャーすることができない。

auto f()
{
    std::vector<int> v(1000) ;

    return [=]() { return std::make_pair( v.begin(), v.end() ) ; }
}

この例で、vは関数fから呼び出し元に戻れば破棄されるオブジェクトである。しかし、lambda式のクロージャーオブジェクトは、関数の呼び出し元に返さなければならないので、リファレンスキャプチャーすることはできない。コピーをしなければならないが、どうせ関数はすぐ呼び出し元に帰るので、変数vはもう必要ないのに、コピーが発生してしまう。ムーブをしたいところだが、C++11のlambda式には、そのための文法がない。

ムーブキャプチャーできない制限は、C++11の策定時に認識されていたが、適切な文法の議論が必要なために、C++11では後回しにされた。データメンバーをコピーキャプチャーできない問題は、C++11を教育する際に、学習者からわかりにくい挙動だと指摘された。

C++14では、この問題を解決するために、初期化lambdaキャプチャーを追加した。これは、lambdaキャプチャーに初期化子を書くことができる機能だ。


void f()
{
    int data = 0 ;

    // コピーキャプチャー
    [ data = data ]{} ;
    // リファレンスキャプチャー
    [ &data = data ]{ } ;
}

このように、キャプチャーの識別子に初期化子を書くことができる。

初期化lambdaキャプチャーは、あたかも、"auto 初期化lambdaキャプチャー"と書いたかのように振る舞う。リファレンスキャプチャーをする場合は、名前の前に&を書く。

初期化キャプチャーでは、名前を好きにつけることができる。


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

また、初期化子は初期化子なので、好きな式を書ける。そのため、キャプチャーというよりは、クロージャーオブジェクトのデータメンバーの宣言に近い。


// 自動ストレージ上にはないオブジェクト
int a = 0 ;

int get_value() ;

void f()
{
    int b = 0 ;

    [ a = a, b = b + 1, c = 1, d = get_value() ] { } ;
}

この機能を使うと、C++11のlambdaキャプチャーにある二つの問題を解決できる。

データメンバーのコピーキャプチャーは、簡単にできる。

struct X
{
    int data = 0 ;
    void get_lambda()
    {   // コピーキャプチャー
        return [ data = data ](){ return data ; }
    }
} ;

int main()
{
    std::function< int() > f ;

    {
        X x ;
        f = x.get_lambda ;
    }// xは破棄されている

    f() ; // OK、
}

また、初期化子を書けるので、ムーブをするコピーキャプチャーも書ける。

auto f()
{
    std::vector<int> v(1000) ;

    return [ v = std::move(v) ]() { return std::make_pair( v.begin(), v.end() ) ; }
}

初期化lambdaキャプチャーは、lambda式をより便利に使うことができる新機能だ。

なおこの機能はGCC 4.9とClang 3.4で実装されている。

Clang - C++1z, C++14, C++11 and C++98 Status

C++1y/C++14 Support in GCC - GNU Project - Free Software Foundation (FSF)

See Also:

本の虫: C++14の新機能: 2進数リテラル

本の虫: C++14の新機能: decltype(auto)

本の虫: C++14の新機能: 関数の戻り値の型推定

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

最近、ドワンゴ社内で売られている昼食の質が向上した。好きなだけ取ってグラム単位で会計する仕組みの昼食販売が導入されたからだ。

野菜が取り放題で、しかも安いので、すばらしい。

さて、筆者が十分に多く盛りつけたサラダを食べて満足していると、いかにもよく肉を食べそうな図体のドワンゴ社員が、「僕、肉しか食べないんで」と言いながら、実際に肉を大量に盛り付けて満足そうにしていた。有限実行の徒である。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-29

妖怪ハウスでスタッフドチキンを作った

本の虫: 妖怪ハウスに不思議なものが送られてきたで報告したように、妖怪ハウスに鶏が丸ごと冷凍で送られてきた。せっかく丸ごとの鶏なので、ふさわしい料理をしたいものだ。しかし、うまくできるだろうか。

今日、意を決して、スタッフドチキンを作ることに決めた。

まず、冷凍の丸鶏を冷蔵庫に入れて一日かけて回答する。解凍できたら、水洗いして、表面に塩コショウをまぶす。

詰め物としては、タマネギと人参とセロリをみじん切りして炒め、数時間水につけておいたもち米を入れて、塩コショウとコンソメスープで煮込んだものを詰め込んだ。

チキンの表面にオリーブオイルをぬる。

オーブンの板に人参とセロリを敷き詰め、チキンを置いて、オーブンで加熱した。

無事出来上がり、なかなかの好評を得た。

スタッフドチキンは、見た目が豪華そうでよい。ただし、食べるために切り分けるのが手間だった。

今回得た経験から考えると、敷き詰める野菜にもっとこだわるべきであった。セロリの葉はこげてしまうので、敷き詰めても意味がなかった。今度からは茎だけにするつもりだ。人参はもっと大き目に切るべきだった。また、じゃがいもやかぼちゃを置くのもいいだろう。また、焼き方も、低温で長時間焼くのがよいだろう。せっかちな住人に度々焼いている途中でオーブンを開けられて、加熱が中断されたのが残念だった。

また作ろうと思う。クリスマスに作るのが、一番ふさわしいのではないかと思う。クリスマスといえば七面鳥だが、七面鳥は高いし、また調理にも時間がかかるので、日本ではチキンのほうが手軽だろう。

C++14の新機能: 関数の戻り値の型推定

C++14では、通常の関数に、戻り値の型推定という機能が加わった。

C++14の戻り値の型推定を解説する前に、まずC++11の話を使用。

戻り値の型推定は、C++11のlambda式に備わっていた。ただし、C++11では、lambda式が戻り値の型推定を行うためには、本体はreturn文ひとつだけでなければならないという制約があった。return文ひとつだけでない場合、戻り値の型はvoid型となる。

// C++11のlambda式
// 戻り値の型はint
[](){ return 0 ; } ;

// 戻り値の型はvoid
[]() { } ;


// ill-formed.
// 戻り値の型はvoidなのにint型を返している。
[]()
{
    int x = 0 ;
    return x ;
} ;

C++14では、通常の関数の戻り値の型推定機能の導入と共に、そのような制限は撤廃された。

// C++14のlambda式
// well-formed
[]()
{
    int x = 0 ;
    return x ; 
} ;

また、C++11では、placeholder typeという機能が導入された。

// C++11のplaceholder type
auto x = 0 ; // int

C++14では、placeholder typeに、decltype(auto)が追加された。

// C++14のplaceholer type
decltype(auto) x = 0 ; // int

decltype(auto)とautoの違いについては、前回の解説を参照。

本の虫: C++14の新機能: decltype(auto)

C++14に追加された通常の関数の戻り値の型推定は、このplaceholder typeを関数の戻り値の型として指定できる。その場合、型はreturn文のオペランドの式から推定される。

// C++14の戻り値の型推定

// void
auto f() { }

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

// int
decltype(auto) h() { return 0 ; }

戻り値の型としてのplaceholder typeは、後置することもできる。

auto f() -> auto
{
    return 0 ;
}

この文法が認められている理由は、主に、lambda式にplaceholder typeを記述するためだ。


// int &
[]() -> auto & 
{
    static int x ;
    return x ;
} ;

decltype(auto)は、主に戻り値の型推定を行わせるために追加された。

int & f() ;

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

// int &
decltype(auto) h() { return f() ; }

C++14の戻り値の型推定には、C++11のlambda式の戻り値の型推定のような、とても使いづらい制限はない。普通に書けば普通に動く。ただし、細かい点で注意すべきところはある。

すべてのreturn文のオペランドの式の型は、一致していなければならない。

auto f()
{
    return 0 ;
    return 0.0 ; // ill-formed. 型の不一致
}

再帰はできる。もちろん、return文のオペランドの式の型は、すべて一致していなければならない。


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

placeholder typeの型推定にあたって、まだ型推定されていないplaceholder typeが現れる場合は、エラーとなる。これは、変数の場合と同等だ。


auto x = x ; // エラー

auto f() ;
// エラー
auto g() { return f() ; }

placeholder typeを使った関数を再宣言する場合は、同じplaceholder typeを使わなければならない。推定される具体的な型を使うことはできない。

auto f() ; // OK、宣言
auto f() { return 0 ; } // OK、定義

auto f() ; // OK、再宣言

// ill-formed
// 同じplaceholer typeを使わなければならない
decltype(auto) f() ;
int f() ;

関数テンプレートの明示的実体化や明示的特殊化でも、同じplaceholder typeを使わなければならない。

// #1
template < typename T > auto f( T t ) { return t ; }

// 明示的実体化
extern template auto f( int ) ; // OK
extern template char f( char ) ; // エラー

// 明示的特殊化
template < > auto f( short ) ; // OK
template < > long f( long ) ; // エラー

// #2
// #2は#1とは異なるテンプレートであることに注意
template < typename T > T f( T t ) { return t ; }

extern template char f( char ) ; // OK, #2の明示的実体化
template < > long f( long ) ; // OK、#2の明示的特殊化

もちろん、上記のテンプレートは、実際に実体化して使う際に曖昧になるが、それは使う場合の話であって、テンプレートの話ではない。

virtual関数で戻り値の型推定機能を使うことはできない。提案論文のN3638によれば、技術上可能ではあるが、オーバーライドのチェックとvtableレイアウトが複雑化するため、現時点では禁止しておくとのことだ。

戻り値の型推定で、std::initializer_listを推定することはできない。

placeholder typeで変数を宣言する場合は、std::initalizer_listを推定することができるが、std::initializer_listは実装の都合上、自動ストレージ上に構築して参照渡しをするため、関数の戻り値として返すのは不適切であるという理由に寄る、

// OK
// std::initializer_list<int>
auto x = { 1, 2, 3 } ;

// エラー
// std::initializer_listは推定できない
auto f()
{
    return { 1, 2, 3 } ;
}

以上、細かい点はあるが、通常の利用では、それほど気にする必要はない。普通に書けば、自然に動くように設計されているはずだ。

See Also:

本の虫: C++14の新機能: 2進数リテラル

本の虫: C++14の新機能: decltype(auto)

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-28

xkcd: マニュアル

xkcd: Manuals

←問題を解決する | 問題を作り出す→

左端:説明書を必要としないツール

中央:説明書を必要とするツール

右1: 説明書を必要とするが説明書が存在しないツール

右2: 説明書が「説明書の読み方」で始まるツール

titleテキスト: もっとも最悪なのはsudoersのmanページだ。クイックガイドから始まって15年たった今や、言語の文法を定義するEBNFで記述されている。「恐れることはない」と説明書は言う。「以下の定義は注釈付きである」と。

ためしにman sudoersをみてみると・・・これはひどい。

2014-09-27

xkcd: 業務

xkcd: Tasks

写真を撮った時、アプリは場所が国立公園かどうかを確認して・・・

GISを参照するだけ、簡単ね。数時間でできるわ。

・・・そして、写真が鳥かどうか確認する。

研究部署を立ち上げる必要があるわね。期間は5年。

コンピューター科学では、簡単なことと、実質不可能なことの差を説明するのが難しい。

さもありなん。

2014-09-26

C++14の新機能: decltype(auto)

さて、前回に引き続き、C++14の新機能を解説していく。今回は、decltype(auto)を解説する。

1

C++14で追加されたdecltype(auto)は、C++11で追加されたauto指定子と同じ、placeholder specifierである。decltype(auto)は、autoでは解決できない問題を解決するために導入された。導入のきっかけは、これまたC++14で追加された、関数の戻り値の型推定機能のためである。これは後日解説する。

decltype(auto)は、わかりやすいといえばわかりやすい。autoのような型が勝手に変わる罠がないからだ。わかりにくいといえばわかりにくい。C++の型システムに深く関わるし、autoのようによきにはからってコンパイルが通ることがないからだ。

さて、C++14のdecltype(auto)について解説する前に、まずC++11のautoについて解説しよう。

auto指定子は変数宣言の型指定子に記述した場合、変数の型は、初期化子から決定される。

auto a = 0 ; // int
auto b = 0.0 ; // double

std::vector<int> v ;
auto iter = v.begin() ; // std::vector<int>::iterator

なるほど、一見すると、とてもわかりやすい。C++では、式の型はコンパイル時に決定できるので、このコードは危険ではないし、実行時オーバーヘッドなども一切ない。以下のように書いた場合と同等である。

int a = 0 ;
double b = 0.0 ;
std::vector<int> v ;
std::vector<int>::iterator iter = v.begin() ;

さて、単にauto指定子を使うだけならば、この程度の理解でもよい。しかし、auto指定子、というよりもC++の型システムは複雑なのだ。

「変数の型は、初期化子から決定される」と書いた。初期化子(initializer)とは、= の右側の式のことだ。


auto variable = initializer ;

「初期化子から決定される」という説明は、あまりにも物事を簡略化しすぎている。より正確に説明すると、テンプレート実引数推定のルールで型推定が行われる

つまり、こういうことだ。

template < typename T >
void f( T ) ;

// 変数variableの型はT
f( initializer ) ;

さきほどの変数variableの型は、あたかも上記のように書いた場合にテンプレート実引数推定の結果のTの型になる。

それの何が問題なのか。テンプレート実引数推定には、型の変換が入るのだ。

int array[10] = { } ;

// decltype(array) = int [10] 
// decltype(x) = int *
auto x = array ;


void function() ;

// decltype(function) = void ()
// decltype(y) = void (*)()
auto y = function ;


int const const_obj = 0 ;

// decltype(const_obj) = int const
// decltype(z) = int
auto z = const_obj ;

特に問題になるのは、リファレンスだ。

int obj = 0 ;
int & ref = obj ;

// decltype(ref) = int &
// decltype(x) = int
auto x = ref ;

lvalueリファレンスの場合は、以下のように書くという手もある。

auto & x = ref ;

残念ながら、rvalueリファレンスの場合は、この方法は使えない。

int & l() ;
int && r() ;

// ん?
// decltype(x) = int &
auto && x = l() ; 

// decltype(y) = int &&
auto && y = r() ;

変数xの型はlvalueリファレンスになる。これは、auto指定子がテンプレート実引数推定のルールを利用しているためだ。そのルール上、初期化子の型から、変換がかかる。

これを防いで、初期化子の型をそのまま使うには、decltypeを使う方法がある。

decltype(expr) x = expr ;

なるほど、たしかにこれは動く。問題は、コード中にexprが重複してしまうということだ。同じコードを機械的に二度書かねばならないし、問題の元だ。

このために、C++14には、初期化子の型をそのまま使う、decltype(auto)が追加された。

int && f() ;

auto x = f() ; // int
decltype(auto) y = f() ; // int &&

decltype(auto)は、autoを初期化子の式で置き換えたかのように振る舞う。つまり、初期化子の型になる。

auto指定子との挙動の違いを比較してみよう。

int array[10] = { } ;

// ill-formed.
// 配列の初期化子に配列は使えない
decltype(auto) x = array ;


void function() ;

// ill-formed.
// 関数型の変数を宣言することはできない
decltype(auto) y = function ;


int const const_obj = 0 ;

// well-formed
// decltype(z)はint const
decltype(auto) z = const_obj ;

また、初期化子の式をdecltypeの中に入れるということは、初期化子が括弧で囲まれていた場合、型が変わる。


int obj = 0 ;

// decltype(x) = int
decltype(auto) x = obj ;

// decltype(y) = int &
decltype(auto) y = (obj) ;

これはdecltypeの仕様である。

See Also:

本の虫: C++14の新機能: 2進数リテラル

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。毎週金曜日の夜はボドゲと相場が決まっている。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-25

C++14の新機能: 2進数リテラル

C++14の標準規格は、無事制定された。そこで、C++14に正式に入ることが確定した新機能を、解説していこうと思う。

今回は、手始めに2進数リテラルを解説する。

2進数リテラルとは、C++14に追加された新しい整数リテラルである。その文法と意味は、文章で解説するよりも、コードを読んだほうが理解が早いだろう。

int main()
{
    int a = 0b11 ; // 3
    int b = 0B11 ; // 3
    int c = 0b1111 ; // 15
    int d = 0b1011 ; // 11
}

これまで、C++の整数リテラルには、8進数、10進数、16進数があったが、新しく2進数リテラルが追加された。

文法は、0b、もしくは0Bに続けて、0か1が連続する。

2進数リテラルは、ビット列演算などをするソースコードをわかりやすく記述するのに役立つ。

主要コンパイラーの対応状況であるが、GCCは独自拡張として、4.3から2進数リテラルをサポートしている。GCC 4.9で正式にC++標準規格としても有効になるように変更された。ClangはGCCより後発で、GCCとの互換性のためにGCCの独自拡張も多く実装しているため、当初から2進数リテラルはサポートしてる。

GCCのC++14実装状況: C++1y/C++14 Support in GCC - GNU Project - Free Software Foundation (FSF)

ClangのC++14実装状況: Clang - C++1z, C++14, C++11 and C++98 Status

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-23

妖怪ハウスに不思議なものが送られてきた

祝日の午前中、平和にのんびりとしていたところ、突如鳴り響くインターホンの声。これはさてや、アマゾンで注文しておいた。 What If: Serious Scientific Answers to Absurd Hypothetical Questionsだろうか。

しかし、予想に反して、お届け物は本を入れるにはやや小さな箱であった。しかも、配達員は冷凍物であると告げるではないか。はて、そんなものを注文した覚えはないが。

住所を確認すると、確かに妖怪ハウス宛になっている。また、宛名は筆者のものだ。すると、筆者宛てに送られてきたものに違いない。

開けてみると、商品名には、GRILLER1300丸鶏と書かれていた。何やら丸まった塊の冷凍肉が入っている。

丸鶏と書いてあるからには、毛をむしり内蔵を取るなどの処理をしたニワトリが一匹まるごと冷凍されているのであろう。

同封の紙の記述によれば、おそらくこの商品が送られてきたはずである。

[The Meat Guy] 丸鶏(グリラー) 1.3kg サイズ

さて、どう料理しようか。

最も安直な方法は、バラバラに切って、普通に鶏肉として使うものである。しかし、せっかくの丸ごとの鶏に、それをするのはもったいない。

ここはやはり、ローストチキンかスタッフドチキンを作ることが期待されているのだろう。幸い、妖怪ハウスにはガスオーブンがあるので、作れるだろう。

ちょうど、住人の蛙君の誕生日が28日にある。その日にでも作ってみようか。あるいは、私の誕生日も10月にあるので、その日に作ってみようか。

ニワトリを丸ごと焼くというのは、見た目にも楽しいので、人が集まる機会に料理してみたいものだ。

2014-09-22

兎のピイタアの話

ビアトリクス・ポツタア

ある所に四匹の子兎が住んでゐて、名前を、フロプシイ、モプシイ、コツトンテイル、ピイタアと云つた。大きなモミの木の根本の砂穴の中に住んでゐた。

「さあ、お前たち」と、ある朝、母ウサギは云つた。「野原や道端には行つてもいいけれど、マクグレガア翁の畑に行つてはいけないよ。父さんはあそこでひどい目にあつたのだから。マクグレガア嫗に料理されてパイになつてしまつたのさ」

「さあ、一緒にお行き。滅多なことをするでないよ。私も出かけるからね」

そして、母兎は編みかごと日傘とを持つて、森を通つてパン屋へと向かつた。母兎はパンを一斤と、人参パンを5個買つた。

フロプシイ、モプシイ、コツトンテイルは、良い子兎で、道々に黒苺を集めた。

ピイタアは、いたずら好きで、まつすぐマクグレガアの畑に駆けて行き、扉の下をくぐり抜けた。

まずはレタスをいくつか食べ、鞘付きの隠元豆を食べ、さらに大根を食べた。

すると、気分が悪くなつたので、パセリを探すことにした。

だが何と、胡瓜の箱植の角で出会つたは、マクグレガア翁に他ならなかつた。

マクグレガア翁は這ひつくばつてキャベツの苗を植ゑてゐたが、飛び上がつて、鋤鍬を振り回し、「止まれ泥棒」と叫びながら、ピイタアを追ひかけた。

ピイタアは極めて恐怖した。扉の場所を忘れてしまつたため、畑中を逃げまわつた。靴の片方はキャベツ畑になくし、もう片方は馬鈴薯畑になくした。

靴をなくしてからは、四足になつて走つたため、一層速く走れるようになつた。グズベリイの網にぶつかつて、上着の大きなボタンをひつかけさえしなければ、逃げおおせたものと余は思ふ。金ボタンの付きたる青上着にて、まだ新しかつた。

ピイタアは諦めて、ひどく泣いた。泣き声は親しげな雀に聞こえ、飛んできて、叱咤激励するのであつた。

マクグレガア翁は笊を持つてやつてきた。ピイタアの上に被せようとする積もりである。ピイタアはちようど、上着を残して、もがき逃れた。

物置小屋に逃げ込み、じようろの中に飛び込んだ。中に水が入つてゐなければ、隠れるにこの上なく都合のいい物であつたろうに。

マクグレガア翁はピイタアが物置小屋の中にいるものと考へた。植木鉢の下にでも隠れてゐるのかも知れぬ。翁は慎重に鉢をひつくり返して調べ始めた。その時、ピイタアはくさめをした。「ハツクシヨン」。マクグレガア翁はすぐさまピイタアを追ひかけた。

さうして、ピイタアを足で踏み潰さうとしたが、ピイタアは窓から飛び降り、植木鉢を三つひつくり返した。窓はマクグレガア翁には小さすぎたし、翁はピイタアを追ひかけるのに疲れてしまつた。翁は野良仕事に戻つた。

ピイタアは休まうと座つた。息を切らせ、怖さに震えてゐた。そして、どこに行くべきか分からずにいた。じようろの中にいたので、ずぶ濡れであつた。やがて、ピイタアは辺りをうろつきはじめた。ふらふらと、ゆつくりと、辺りを見て廻つた。

壁に扉を見つけたが、鍵がかかつていて、丸々と肥えた子兎がくぐり抜けることはできなかつた。一匹の古鼠が、ドア下の石畳の上を出入りし、森に住む家族のために、豆を運んでゐた。ピイタアは扉への道をたずねたが、この雌鼠は大きな豆を口の中に入れているため、答えなかつた。雌鼠は首を振つた。ピイタアは泣きだした。

さうして、ピイタアは畑を一直線に突つ切ることで道を探さうとしたが、ますます迷つてしまつた。さて、ピイタアは、マクグレガア翁がじようろの水を汲むためのため池に突き当たつた。白猫が金魚を見つめてゐる。この雌猫は座つたままぴつたりと動かなかつたが、尻尾だけは、別物のように動いた。ピイタアは話しかけぬ方が賢明であらうと考へた。猫のことは、いとこの子、ベンジヤミン兎から聞いてゐた。

ピイタアは物置小屋の方に戻つたが、急に、とても近くで、鍬の音が聞いた。ザク、ザク、ザク、ザク。ピイタアは茂みの下に隠れた。しかし、何事もなかつたので、出てきて、手押し車に登つて覗き見た。マクグレガア翁が玉葱畑を耕しているのが、まず目に入つた。翁はピイタアに背を向けてゐて、その後ろに、扉があるではないか。

ピイタアは静かに手押し車から降りると、全速力で、カシスの茂みから一直線に走りだした。マクグレガア翁は曲がり角でピイタアを見つけたが、ピイタアは意に介さず。ピイタアは扉の下をくぐり抜けて、ようやく、畑の外、安全な森の中に出た。

マクグレガア翁は小さな上着と靴を吊るして、鳥を脅かす案山子となした。

ピイタアは大きなモミの木の家に辿り着くまで、止まらず、後ろを振り返ることもなかつた。とても疲れてゐたため、兎穴の柔らかい砂の上に寝転がると、目を閉じた。母親は料理に忙しかつた。母親は服をどうしたのかと不思議がつた。この二週間で、服と靴をなくしたのは二度目なのだ。

余は、その晩のピイタアは気分が優れなかつたと語らねばならぬ。母親はピイタアを寝床に寝かせ、カモミイル茶を沸かして、ピイタアに一服飲ませた。「寝る前に匙一杯よ」

フロプシイ、モプシイ、コツトンテイルは、パンと牛乳と黒苺の夕食をした。終わり。

Boost勉強会#16大阪

去る2014年9月20日に、Boost勉強会#16大阪に参加して、発表してきた。

Boost.勉強会 #16 大阪 - Boost.勉強会 大阪 | Doorkeeper

東京から大阪へは新幹線で3時間未満のため、当日の早朝に家を出て現地に向かうこともできたのだが、遅くとも午前6時には、いや余裕を得るためには午前5時には家をでなければならないことを考えると、前日に京都まで行ってしまうことにした。

京都には実家もあるし、適当な宿を取ってもいいのだが、やはり吉田寮に泊まることにした。京都はやけに寒かった。東京は、まだ暑いぐらいであるが、京都はもう寒いものなのだろうか。それとも、たまたまこの数日が寒いのであろうか。

20日は、吉田寮生の岡田君と一緒に、大阪まで出かけた。

我々は梅田駅を避けて、中津駅で降りた。地図上で確認すると、中津駅の方が会場に近いと判断したためであるが、かの悪名高い迷宮、梅田駅を避ける目的もあった。

さて、我々は中津駅より南下し、会場の住所である大阪市北区芝田2-9-20近辺に到達した。しかし、会場が見つからない。建物や電柱に記載されている住所によれば、芝田2-9-30は、我々のいるたかだか数十メートルの一区画であるはずだが、一向にベルスクールという建物は見当たらない。さんざん周辺を歩きまわった結果、学園ビルという名称の建物の中に、ベルスクールは存在した。

さて、せっかくの勉強会であったが、当日の筆者は、発表があったので、資料の確認と修正に余念がなく、他人の発表には注意が向かわなかった。今回の筆者の発表は、軽量コンセプトだ。当日使用したスライド資料はGitHubに上げてある。

EzoeRyou/boost-benkyokai-oosaka-16

軽量コンセプトのドラフトTSは、今まで真面目に読んでいなかった。今回、資料作成のために初めてまともに読んだが、従来のテンプレートメタプログラミングの薄いシンタックスシュガーという感想を受けた。これが採用されるよりは、C++0xに提案されていた当時のコンセプトが入って欲しい。

当時のコンセプトより優れている点として、任意の式にsubstitutionが試みられるとか、任意の式が定数式や無例外式であるかどうかや、式を評価した結果の型という点で制約を記述できる部分だ。実に泥臭い低級な記述だが、当時のコンセプトよりはよほど高機能で現実的で、求められている機能ではあると思う。

今回のスライド資料は、簡単で具体的な使用例と、詳細の説明という二部構成にしたが、詳細な説明は、ブログ記事に書くのはいいが、聴衆の前で短時間で話すには不向きであったかもしれない。

いい機会なので、このスライドは加筆し、もっと具体的な使用例を増やして、次の機会に発表したいものだ。

というわけで、年内に、もう一度東京でC++の勉強会を開くべきかもしれない。

さて、勉強会は終了した。せっかくの連休なのだから、京都にとどまって、月曜日のC++ Templatesの邦訳の読書会に参加するという選択肢もあったが、そもそもあの本は10年前ならともかく今読んでも古すぎるし、あいにくと筆者はくしゃみと鼻水に悩まされていたため、早々に帰宅した。しかし、帰りの新幹線の中で、体調は完全に治った。どうも体調を崩したのではなくて、何らかの一時的な原因があったのではないか。もしかしたら、吉田寮でホコリとダニとノミにやられたのかもしれぬ。あるいは、やや肌寒い中、プレハブ小屋で何も布団をかけずに寝ていたためかもしれぬ。

さて、そういうわけで、今日明日は東京で暇をしている。どこかに遊びに行こうかとも思っているが、私には外に遊びに行くという動機がない。あえて考えれば、神保町に古本を漁りに行くぐらいだろうか。

ドワンゴ広告

この記事はドワンゴ勤務外に書かれた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-17

電車を盛大に乗り過ごした

会社に出勤すべく家を出て、東西線に乗った。幸い、今の仕事には定時という概念が無いため、ラッシュの時間帯を避けることができる。そのため、電車の中はすいており、座ることができた。

周りの人間が不自由なバカフォ操作に勤しむ中、筆者は通勤中に紙の本で読書をしたり、昔の不自由なゲーム専用機で遊んだりしている。今は、カサノヴァ回顧録を読んでいる。

ジャコモ・カサノヴァは真の自由人である。彼は自己の自由を制限する危難に何度も遭遇しつつ、そのたびにあふれる才気と運で自由を勝ち得てきた男である。

物語は、鉛の監獄から、まさに脱出するところであった。カサノヴァは唾棄すべき卑屈な間諜の精神を叩きのめして屈服させ邪魔を排除し、やはり唾棄すべき救いようのない糞坊主を操縦して、天井に穴を穿ち、宮殿の鉛の屋根を伝って窓をぶち破る。

ふと顔を上げると、なぜか窓の外が明るい。地下鉄であるはずの東西線が地上を走っている。どうやらだいぶ乗り過ごしたらしい。次の停車駅を見ると、なんと浦安となっている。

さて、岩波文庫のカサノヴァ回顧録も、残すところ後一冊となった。残念ながら、翻訳者の岸田國士が翻訳作業中に死んでしまったので、岩波文庫のカサノヴァ回顧録は未完に終わっている。

ドワンゴ広告

この記事はドワンゴ勤務中に書いた。こんな記事でも多少の宣伝効果は得てんや。覚つかなし。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-16

最近の料理事情

しばらくブログには書いていなかったが、折を見ては、新しい料理を試している。

今日はトマトカレーを作った。作り方を調べると、ホールトマト缶で煮込んでカレー粉を加えるのだそうだ。試しに作ってみたが、なかなかうまかった。ただし、トマトの味が強すぎて、むしろトマト煮ではないかとの感想も得た。

プレーンヨーグルトにドライフルーツをまぜ込んで一晩置くと、ドライフルーツがふやけてヨーグルトが甘く美味しくなるというのは、すでに試したが、今はさらに一歩進めて、その出来上がったドライフルーツヨーグルトを、アロエにかけている。なかなかいいスイーツ(笑)となる。

また、保存のきく面白そうな食材も、ちょくちょく買っている。いずれ試さなければなるまい。

とうがらしパスタと、イカスミパスタが売られていたので、買ってみた。まだ試していないが、いずれゆでてみよう。

また、この前に買った大豆ミートは、未だに調理する機会がない。保存はきくので、いずれ。

2014-09-15

mp3fs : FUSEを利用したファイルシステム経由のmp3変換

mp3fs

mp3fsとは、FUSEを利用したリードオンリーのファイルシステムである。これは、ファイルが開かれた際に、FLACからMP3へのフォーマット変換をその場で行うものである。

用途としては、FLACの音楽集を、MP3しか対応していないソフトウェアやハードウェアで再生する場合に使える。

Hacker Newsのコメントでは、case-insensitiveなファイルシステムを作ったとか、大学の課題でmp3ファイルからID3タグを得て返すファイルシステムを作ったなどのコメントが上がっている。

ファイルシステムを経由した組み合わせ可能(composable)なツールというのは、従来のパイプより可能性が広がるので、なかなか面白い。

CppCon 2014のスライド資料

CppCon 2014のスライド資料が公開されている。

CppCon2014/Presentations at master · CppCon/CppCon2014

雑記

この土日は、主に昼寝と読書をして怠惰に過ごした。

読んだ本であるが、あまり重いものを読む気分ではなかったので、すこし前に買ってあった、ピーターラビット全集を読むことにした。たまたま、ブックオフで原書の全集が3000円で売っているのを見つけたのだ。だいぶ迷ったが、見つけたのも何かの縁と思って買うことにしたのであった。

買ってすぐに、お気に入りの話を何冊か読んだままで放置していたが、この機会に通して読むことにした。

ピーターラビットの英語は、110年前のものなので、さすがに古めかしい言い回しもあるものの、だいぶ読みやすい。

ただし、歌にはどのような節回しを付けるべきなのか分からない。

日曜日は、神保町に行って古本を漁るとか、秋葉原に行ってRaspberry Piを買いたい衝動に駆られたが、読む本はまだ残っているし、Raspberry Piも買ったところで何に使うあてがあるわけでもないので、結局行かなかった。

日曜日の夜は酔っぱらいが来たので、夜遅くまでくだらない雑談をしていた。

さて、月曜日こそはどこかに出かけたいが、どこに行くというあてもない。

2014-09-12

三連休

明日から三連休だ。たまたま妖怪ハウスにビールと氷結が余っているので、この週末に飲みに来る人がいればどうぞ。金曜日の夜は遅くまで帰らないが、三連休はだいたい家にいるはずだ。

cpで大量のファイルをコピーした(4億3200万件、39TB)

GNUのcpを使って大量のファイルをコピーしたところ、cpの設計上の問題で、極めてコピーが遅かったというお話。

My experience with using cp to copy a lot of files (432 millions, 39 TB)

よう。俺は最近、大量のファイルをコピーする必要があったんだが、UNIXは20年もやってきた俺の経験からも、cpの挙動には驚かされたし、俺の意見はコミュニティに共有されるべきだと思う。

環境:古いDellのサーバー(2コア、初期メモリ2GB、追加して10GB、Ubuntu Trusty)と、新しいDellのストレージ格納機(MD 1200)にある、12個の4TBディスクでRAID 6が設定してあって、全体で40TBの要領を持ち、二つのドライブが同時に失敗しても問題ない環境。サーバーは遠隔地バックアップに使われていて、ディスクへの書き込みしかしてない。ほとんどのファイルにrsnapshotをかけてるので、リンク数が多い(30+)

ある朝、俺はディスクが故障している通知を受け取った。まあ問題ない。たまにあることだ。Dellに電話して、次の日に交換ディスクを受け取った。リビルド中に、交換ディスクが故障した。そして、また別のディスクも故障した。さて、Dellのサポートは単に故障ディスクを交換するのは辞めるように支持してきた。というのも、全体があなぬけである可能性があるから。もちろん、俺も分かっていることだが、ディスクというものは十分に多くの不良ブロックがないと故障だと通知されない。運が悪ければ、異なるディスクの三つのブロックが短期間でやられたの可能性があり、RAIDコントローラーは失敗を検知して、パリティからデータを再計算して、どこか別の場所に記録するということができていないかもしれない。そういうわけで、ヤバイディスクは二つだけだが、データが失われた可能性はある。

容量もほぼ使いきっていたことだし、ストレージ格納機をもうひとつ注文して、古いのから新しいのにファイルをコピーして、古いのを直して、全体の容量を増やそうと考えた。普通ならば、ブロックレベルでコピー/ムーブするところだが(ddとかpvmove)、不良ブロックの疑いがあるので、ファイルレベルのコピーをすることにした。そうすることによって、どのファイルが不良ブロック上にあるのか分かるからだ。他人が大量のファイルをコピーする経験をネット上で検索した結果、cpは必要な処理をやってくれるだろうと結論した。ハードリンクを保つには、どのファイルがすでにコピーされたかを把握しなければならないからだ。俺はサーバーに8GBのRAMを増設し、スワップ領域ももっと多く取るように設定した。

新しい機材が届いたので、コピーを始めた。当初は、iotopで計測すると、300-400MB/sぐらいの良好な進み具合だった。しばらくすると、速度が極端に下がり始めた。ほとんどの時間は、ハードリンクを作成するのに費やされ、ファイルシステムが整合性の取れた状態であることを保証するのには時間がかかるからだ。俺はXFSを使っている。たぶん、write barrierを無効にするのを忘れていたためだろう。RAIDコントローラーには信頼できるバッテリーバックアップ付きのwrite cacheがあるので、無効にしても問題なかったはずだ。予想通り、cpのメモリ使用量が上がり始め、すぐにギガバイト単位に達した。

コピーを開始してから数日後、驚き第一段がやってきた。コピーが止まったのだ。straceによると、cpは一切のシステムコールを発行していなかった。ソースコードを読むと、cpは、どのファイルがコピーされたのかを、ハッシュテーブルで管理していて、頻繁な衝突を防ぐために、たまにサイズを増やしている。RAMを使い切った場合、これは極めて遅い操作となる。

ハッシュテーブルのリサイズはいずれ終わり、cpは処理を再開するだろうと考えた。しばらくすると、またコピーを始めた。何度か途中で泊まり、ハッシュテーブルのリサイズが行われた。それぞれ、かなりの時間がかかった。ついに、10日間ものコピーとハッシュテーブルのリサイズの末、新しいファイルシステムは、古い奴と同量のブロックとinodeを持ったことを、dfで確認した。しかし、驚いたことに、cpコマンドは終了しない。またソースを読むと、cpはハッシュテーブルのデータ構造を、コピーが終わった後でご丁寧にも破棄しようとしている(forget_all呼び出し)。この時点でcpプロセスの仮想メモリのサイズは17GB以上あり、サーバーには10GBのRAMしかないため、大量のスワップが発生する。

cpは"-v"オプション付きで実行されていて、出力(stdoutとstderr両方)はteeコマンドにパイプして(巨大な!)ログファイルに格納してある。どうやらどこかでcpの出力はバッファーされていて、ログファイルは行の途中で終わっている。バッファをフラッシュさせて完全なログファイルを得たいがために、cpがハッシュテーブルを破棄し終わるまで一日ほど待ったが、諦めてプロセスをkillした。

これを書いている今、両方のファイルシステムに対して"ls -laR"を実行していて、すべてコピーされたかどうかを確認中だ。ただ、cpの失われた出力の最後の部分にエラーが含まれていたのでもなければ、I/Oエラーを出したのはたったひとつのファイルだけだ(運良く、そのファイルには別のコピーがあった)

こういう状況はめったに起こるものではないが、cpは管理だけの処理で待ちぼうけを食らわせるのではなく、I/Oを待つ間に管理処理を行うべきではないか。それから、まともなメモリ管理が行われていない大昔のシステムをサポートする必要があるのでもなければ、cp.cの最後のforget_all呼び出しは削除すべきしても問題ないのではないか。

今回得た知見をまとめると。

ハードウェアとファイルシステムがまともであると信頼するのであれば、ファイルシステム全体をコピーするにはブロックレベルのコピーを行え。空き容量が大量にあるのでもなければ、その方が速い。また、メモリ使用量も少ない。

多くのファイルがあって、ハードリンクを保持したい場合、ファイルレベルのコピーを行う場合は、十分なメモリを用意しろ。

データ構造をご丁寧に破棄することは、単にプロセスを終了させて後始末を任せるより、相当に時間がかかる。

故障したハードディスクの数と、不良ブロックのあるハードディスクの数は、別物だ。RAID 6においても、運が悪ければ、3つのハードディスク不良を待たずして、データを失うこともある。RAID 5でも同じだ。一つのディスク不良か、ディスク不良なしでも、運が悪ければデータを失うことがある。

これが誰かの役に立つか、興味を引くといいな。

敬具など

Rasmus Borup Hansen

cpで大量のファイルをコピーする際の問題は、たまにハッシュテーブルをリサイズするが、その間にI/Oを一切行わないというのと、終了時にご丁寧にもハッシュテーブルで確保したメモリを破棄しようとするため、恐ろしいほど時間がかかるということだ。近代的なOSはメモリ管理をしっかりとしているので、今すぐ終了するのであれば、細切れにしたヒープの中身(しかも実メモリが足りないためスワップ発声)をわざわざ意味のない状態にしていく必要はない。

なお、MLでは、これを受けてcpを改良する動きが見られる。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

来週末はBoost勉強会大阪で発表する予定で、そのためにConcept Liteのドラフトを読み込んでいる。軽量コンセプトはある程度理解した。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-11

Old New Thing: ブルースクリーンを書いた人間は、まあ、僕さ

Old New Thingの著者で、古参MS社員のRaymond Chenが、Windows 95のブルースクリーンを書いた時のことをブログに書いている。

I wrote the original blue screen of death, sort of - The Old New Thing - Site Home - MSDN Blogs

この前、Windows 95の話を持ちだした。ここで書いたように、Ctrl + Alt + Delダイアログは、Windows 3.1で導入され、Windows 95では、すでになくなった。Windows 95では、Ctrl + Alt + Delを押すと、以下のようなダイアログボックスが表示される。

Close Program × 
Explorer
Contoso Deluxe Composer [not responding]
Fabrikam Chart 2.0
LitWare Chess Challenger
Systray
WARNING: Pressing CTRL+ALT+DEL again will restart your computer. You will lose unsaved information in all programs that are running.
End Task
Shut Down
Cancel

(Systrayについては以前学んだよね)

Windows 3.1では、深刻なエラーではクラッシュしてブラックスクリーンになったが、Windows 95では、青で表示するようにした。僕はそのコードを書いたうちの一人だ。少なくとも、最後に書き換えた人間だな。

私はブルースクリーンメッセージを表示するコードを担当していた。カーネルモードビデオドライバーにテキストモードに切り替えるよう指示し、画面を青背景にして、白文字を表示して、ユーザーがキー入力をするのを待ち、画面を元に戻し、ユーザーの入力を、メッセージを表示するよう要請したコンポーネントに伝える[1]。

デバイスドライバーがクラッシュした場合、Windows 95はカーネルモードコンポーネント内の深刻な問題に、最善を尽くして対処しようとする。死の青画面(blue screen of death)というべきなのか、不便な青画面(blue screen of lameness)というべきなのか。ブルースクリーンを見たことがない幸運なもののために、以下がその例だ。



 Windows 


An exception 0D has occurred at 0028:80014812. This was called from 0028:80014C34. It may be possible to continue normally.

* Press any key to attempt to continue.
* Press CTRL+ALT+DEL to restart your computer. You will
  lose any unsaved information in all applications.

「通常通り動作を実行できるかもしれませんよ」という楽観的な文面に注目されたい。みんな忘れているが、Windows 95は、ブルースクリーンエラーを表示した後、なるべくそのエラーを無視して実行し続けようとすることだ。つまり、まあ、スキャナーのドライバーはクラッシュしたので、スキャナーは動かなくなったかもしれないけれど、それ以外のシステムは無事だというわけだ。

(これを今日おこなったらどうなるだろうか。「カーネルパニックを無視するには何かのキーを押してください」)

技術的に何が行われているかというと、バーチャルマシンマネージャーが現在行われているイベントを破棄して、イベントディスパッチャーに戻るのだ。これは、カーネル内における、例外をウインドウプロシージャで飲み込んで、メッセージループに戻るようなものだ。継続できるイベントがなければ、現在のアプリケーションが終了される。

時に、問題はシステム全体に及ぶこともあり、現在のイベントを破棄したり、アプリケーションを終了しても、問題を解決しないことがある。次に起こるのは、次のイベントやアプリケーションが、同じ問題に引っかかり、数ミリ秒後に、またブルースクリーンメッセージを目にするということだ。何回か似たようなメッセージを目にした後、おそらくは諦めて、Ctrl + Alt + Delを押すだろう。

さて、当初のメッセージは、上のようであった。しかし、メッセージには問題上がる。デバイスドライバーがカーネルに読み込まれるアドレスというのは予測可能ではないので、生のアドレスが表示されていても、大して役に立たない。もし誰かが、「うちんとこの重役がこんなクラッシュメッセージ出したんだけど、何があったか分かるかい?」と言われたとしても、得られるのは無意味な数値だけだ。

その誰かというのは、僕でもあったわけだ。

この問題に対処しやすくするために、僕はメッセージを書き開けて、ドライバーの名前と、セクション番号と、セクションからのオフセットを追加した。



 Windows 


An exception 0D has occurred at 0028:80014812 in VxD CONTOSO(03) + 00000152. This was called from 0028:80014C34 in VxD CONTOSO(03) + 00000574. It may be possible to continue normally.

* Press any key to attempt to continue.
* Press CTRL+ALT+DEL to restart your computer. You will
  lose any unsaved information in all applications.

これで、クラッシュしたドライバーの名前が分かる。何もわからなかったとしても、問題がどこにあるかのヒントは得られるわけだ。ドライバーのMAPファイルにアクセスした誰かさんは、アドレスを探して、クラッシュした箇所を特定できる。最高ではないにせよ、何もないよりマシだ。僕がこの変更をするまでは、何もなかったのだ。

そういうわけで、僕は不便なブルースクリーンを、僕の仕事を多少マシにするために書き換えたと言える。

余話:その後、誰か(僕だったかどうかは覚えていないので、誰か同僚としておこう)が、クラッシュアドレスを調べるためのコードを追加した。もし、箇所がカーネルのヒープマネージャー出会った場合、メッセージは多少変更される。



 Windows 


A 32-bit device driver has corrupted critical system memory, resulting in an exception 0D at 0028:80001812 in VxD VMM(01) + 00001812. This was called from 0028:80014C34 in VxD CONTOSO(03) + 00000575.

* Press any key to attempt to continue.
* Press CTRL+ALT+DEL to restart your computer. You will
  lose any unsaved information in all applications.

この場合、「通常通り動作を実行できるかもしれませんよ」という文面が消える。なぜなら、この場合、そういうことはまずないからだ。

余話:よくやったな。Slashdot君。訂正をしたつもりかもしれないが、その訂正も間違っているよ。まあ、間違いには気がついたみたいだけどね。

[1]: このコードはカーネルで動くので、キーボードレイアウト情報にはアクセスできない。読者がChinese-Bopomofoキーボードレイアウトを使っているかどうかなんてわかりっこない。その場合、"OK"とタイプするには、C, L, 3だ。特に訳には立たないけどね。カーネルにはIMEなんてないんだから。そういうわけで、入力は、EnterとかESCみたいな、言語からは独立したキーに割り当てられていた。

だいぶ久しぶりにOld New Thingを訳した気がする。以前はだいぶ苦労していた翻訳も、今はそれほど苦労しないのは、英語力が上がったからであろうか。それとも単に慣れたのか。

2014-09-09

寄生獣に感じるデジャブ

駅のホームの広告で、寄生獣が映画化されるという広告を見た。はて、寄生獣、確か妖怪ハウスにマンガがあったはずだ。

妖怪ハウスに寄生獣が転がっていたので読んでみた。なるほど、面白い漫画だ。しかし、なにか引っかかる。似たような話を昔読んだ記憶があるのだ。

たしか、私の通っていた小学校の図書館にあったSF小説だ。宇宙から飛来した宇宙人が人間の体の中に入って、傷口を止血したり、網膜に影を作って文字を表示して宿主と会話したりしていた。設定が似ているし、他の犯罪宇宙人の宿主の人間を探すという話の筋まで似ている。

ほのかに覚えている内容では、「主人公は腕を深く切り裂いてしまったが宇宙人が止血、軽いキズに見えたので、消毒液をたらすと宇宙人が激痛に飛び退いて、申告な傷となった」、「泥水に躊躇せずに飛び込んだ人間が宿主ではないのかと疑う」、「主人公ははしゃいで裸足で走ったので、ガラスの破片が足にささったが、宇宙人が対処した」など。

しかし、タイトルが思い出せない。誰かこのようなSF小説に心当たりはないだろうか。

もちろん、設定や筋書きなどはアイディアでしかなく、表現ではないので、著作権侵害だと糾弾するつもりはない。ただ、不思議なデジャブ感を何とか解決したいものだ。

2014-09-06

What if? : 高額な靴箱

Expensive Shoebox

サイズ11の靴箱に詰め込める最も高額なものって何? (例えば、64GBのMicroSDカードに大量の購入済み音楽を詰め込むとか)

Rick Lewis

靴箱に高額なものを詰め込んだ結果の上限は、どうやら20億ドルぐらいだ。面白いことに、この上限は高額な候補どれでも同じぐらいだ。

MicroSDカードはいいアイディアだ。iTunesの音楽はひとつあたり1ドルぐらいかかり、MicroSDカードは1ガロン(3.785411784立法メートル)あたり1.6ペタバイトの容量を持つ。男性用サイズ11の靴箱は、靴のブランドや種類により異なるが、だいたい10-15リットルぐらいだ。つまり、4MBの音楽を15億曲(それぞれ約1ドル)となる。(これは、ITunesが提供する楽曲の20倍の個数であり、同じ曲を何度も買わなければならないだろうが)

Adobe®©™ Photoshop®©™ CS®™ 5™のような高額なソフトウェアは、メガバイトあたりの価格率で、多少上回る。ただし、数百ドルで数百メガバイトの容量がある。いや、かつてはそうだった。Adobeは今や、クラウドモデルに移行してしまった。

ソフトウェアの価格を考察にいれると、好きなだけ無制限にアプリ内課金ができるものによってコストを水増しできる。ゲーム内のRPGキャラは廃課金の結果の産物であるとしても、そのキャラが1兆ドルの価値を持つと真面目くさった顔で主張するのは、いかにも無理がある。

親愛なるアメリカ国民よ。我らの国家負債が期せずして2兆ドル跳ね上がった。

ところで、全く関係ないんだけどさ、俺のキャラは超かっこいい剣を大量に持ってるんだぜ。

というわけで、現物を考えてみよう。

もちろん、金というものがある。13リットルの金は、本記事を執筆している時点で、約1000万ドルの価値がある。プラチナは、もう少し高くて、1靴箱あたり1300万ドル[1]だ。これは、靴箱に100ドル札を詰めるより10倍の価値があるが。ところで、靴箱いっぱいの金は、子馬ほどの重さがある。

[1]: 残念ながら、まだSI単位ではない。

もっと高額な金属はある。例えば、1グラムの純粋なプルトニウムは、約5000ドルの価値がある。ボーナスとして、プルトニウムは金より密度が高いので、靴箱に300キログラムほど詰められる。

読者が早速にもプルトニウムを30億ドルほど購入しようと考えているのであれば、注意してもらいたい。プルトニウムの臨海量は10キログラムである。そのため、確かに、靴箱の中に300キログラム詰め込めるとはいえ、それはほんの一瞬の間だけだ。

高品質なダイヤモンドは高額であるが、適正価格を定めるのが難しい。というのも、業界全体が詐欺によって成り立っている宝石市場は複雑だから。あるWebサイトはフローレス600mg(3カラット)のダイヤモンドに30万ドル以上もの値を示している。つまり、完全な品質のダイヤモンドを詰めた靴箱は、200億ドルであるが、10億から20億ドルぐらいが妥当な価格だろう。

さて、同一の大きさのラウンドカットダイヤモンドを最も効率的に詰める方法は。

12時間待て。

違法な薬物は、重さあたりで、金より価値がある。コカインの価格はばらつきがあるが、多くの地域で、グラムあたり100ドル[2]近辺だろう。金は現在、その半分の価値しかない。しかし、コカインは金より密度が低い[3]ため、靴箱いっぱいのコカインは、金より価値が低いだろう。

[2]: ドラッグの末端価格について調査した後の私の検索履歴により、筆者は政府のあらゆる種類のウォッチリスト入りすることだろう。このブログに必要な過去の調査のためにすでにリスト入りしていなければの話だが。

[3]: でも待てよ。コカインの密度ってどのくらいだ。いつも通り、Straight Dope掲示板で議論されている。このスレでは、CRC HandbookやMerck Indexを参照したが、結局諦めて、多くの有機物が取りうる約1kg/Lであろうと結論している。ただし彼らは、コカインの沸点と、オリーブオイルに対する溶解度を学んだようだ。

コカインは重量あたり最も高額なドラッグではない。LSD--おそらく、マイクログラム単位で販売される中でもっとも消費されているもの--は、重量あたりコカインの約千倍の価値がある。靴箱に純粋なLSDを詰めると、約25億ドルの価値がある。

一部の処方箋医療薬は、LSDと同じぐらい高額である。一回分のブレンツキシマブ ベドチン(アドセトリス)は、13500ドルかかる。これは、平均的な患者にとって、靴箱をLSDやプルトニウムやMicroSDカードと同じぐらい高額にできるということだ。他の医療薬には、もっと高額なものもある。

もちろん、靴箱には靴を入れることもできる。

変なの

オズの魔法使いで演じたJudy Garlandsの靴は、オークションで666000ドルの値がついた。この靴は、我々が考察してきた様々なものと違って、かつて靴箱に入っていたかもしれない。

靴箱を任意のお金でいっぱいにしたいのであれば、アメリカ合衆国財務省に10兆ドルのプラチナ硬貨を鋳造させることができる。

とはいえ、通貨の権威者に任意の物体に価値を生じさせるのであれば・・・

単に小切手を書けばよい。

2014-09-04

2014-07-post-Rapperswilのレビュー: N4109-N4121

2014年7月の論文集の最後。

[一発目からPDF] N4109: A proposal to add a utility class to represent expected monad - Revision 1

HaskellのEitherに似たライブラリ、expected<T, E>の提案。expectedは、T型か、あるいはエラー通知のためのE型を格納するクラスである。

前回からの変更点で最も大きなものは、テンプレート仮引数の順番を変えた。expected<E, T>だったものを、expected<T, E>にした。

[論文のpreconditionとしてPDFを禁止したい] N4110: Exploring the design space of contract specifications for C++

C++のコア言語で契約(contract)をサポートする提案。

関数の宣言に契約を指定して、契約に違反した場合、std::terminateを呼び出す。

用語の説明。

エラー状態
プログラムが復帰できるかできないかにかかわらずエラーとなる状態
Precondition
関数を実行する前に満たしている必要のある状態
postcondition
関数を実行後に満たしているべき状態
invariant
関数の実行前、実行後に満たしているべき条件のkと
契約(contract)
ある関数に指定された一連のprecondition, postcondition, invariantのこと。
契約違反
関数を実行前にそのpreconditionかinvariantが満たされていないこと、もしくは、関数実行後にpostconditionかinvariantが満たされていないこと。

契約をコア言語でサポートするために、関数の宣言として、precondition, postcondition, invariantを記述できるようにし、契約違反であれば、std::terminateを呼び出す。あるいは契約違反ハンドラーを設定できるようにする。

論文では、すでに契約をサポートしている言語として、Eiffiel, Ada2012, Dを挙げ、サンプルコードを示している。

またC++にはすでに、防衛的プログラミングという名前で高級なassertマクロが提案されている。

論文は、ライブラリによる契約の実装では、ツールによる解析や、コンパイラーが契約条件を把握することによるよりよいコードの生成などができないとしている。

だいぶ野心的な提案だが、すでにサポートしている言語は複数あるので、机上の空論というわけでもない。ただ、記述するのは面倒そうだが。

[PDFバリヤー、PDFは跳ね返る] N4111: Static reflection (rev. 2)

静的リフレクション機能の提案の一つ。クラスのメンバーなどといったプログラムの構造をテンプレートメタプログラミングの要領で取得できるライブラリの提案。論文としてはN3996の改訂版だが、内容的には全面的に書きなおしになっている。

ただ問題は、テンプレートメタプログラミングの延長線上でプログラムの構造を取得するようになっているので、設計がBoost.MPLもびっくりの複雑なものになっているし、実際に使うのも冗長なコードを書かねばならないだろう。

個人的には、機能的には面白いものの、文法が悲惨すぎて破綻する印象しかない。たしかに、テンプレートメタプログラミングの要領で操作できるのは魅力的でもあるのだが、その文法があまりにも冗長すぎる。

[論文にPDFフォーマットを使うなというNBコメントを出したい] N4112: File System PDTS National Body Comments Record of Response

File System TSに対するNBコメント。

[静的リフレクションの前にPDFを何とかして欲しい] N4113: Reflection Type Traits For Classes, Unions and Enumerations (rev 3)

静的リフレクション機能として、テンプレートメタプログラミングの要領でプログラムの構造を取得できるライブラリの提案。こちらはN4027の改訂版。

これはまだ小規模で理解可能な範囲だ。

N4114: Defaulted comparison operators

クラスの演算子==, !=, <, >, <=, >=をデフォルト生成する機能を追加する提案。明示的なデフォルト演算子。

クラスのメンバーの等号や大小比較ができるとき、メンバーごとの比較をして等価か大小を決定する演算子オーバーロードを書くのは、極めて機械的で面倒な作業である。いちいちメンバーを手で列挙しなければならないし、特に大小比較の場合は、極めて面倒なコードを書かなければならない。そのような作業は、コンパイラーにデフォルトの実装として生成させてしまえばよい。

この演算子を自動生成させるには、明示的に指定する必要がある。なぜならば、いままで存在しなかった演算子が勝手にデフォルト定義されてしまっては、既存のコードを壊す恐れがあるからである。

会議の結果、以下の二つの文法が提案されている。

struct Thing
{
    int a, b, c;
    std::string d;
};

bool operator==(const Thing &, const Thing &)= default;
bool operator!=(const Thing &, const Thing &)= default;

このように、explicited defaultedの文法を流用して、明示的に実装を生成するように指示する。

メンバーがprivateの場合は、演算子はfriendを指定子なければならない。その場合は、以下のように書く。

class AnotherThing
{
    int a, b;

public:
    // ...

    friend bool operator<(Thing, Thing) = default;
    friend bool operator>(Thing, Thing) = default;
    friend bool operator<=(Thing, Thing) = default;
    friend bool operator>=(Thing, Thing) = default;
};

会議による議論では、このように演算子を個別に冗長に指定するのは面倒であるので、もっと短く書きたいという意見が多かった。会議で提案された短い文法は以下の通り。

struct Thing
{
    int a, b, c;
    std::string d;

    default: ==, !=, <, >, <=, >=;
};

この簡潔な文法は、冗長でコピペを誘発する文法に比べて優れているし、最適な引数の型などは、コンパイラーがよきにはからって正しく、パフォーマンス上優位になるよう、決定してくれる。

その他の演算子も同様に生成できるのではないかという点については、論文では、それほど役に立たないとしている。

論文では、mutableメンバーの扱いをどうするかで、議論が沸かれているとしている。解決方法としては、mutableメンバーは比較対象から除外するか、除外せずに対等に扱うかだ。論文著者は除外する方を好むとしている。標準化委員会のデッドロックを避けるために何らかの決定をせねばならず、提案では、mutableメンバーを含むクラスの場合は、デフォルト実装の生成は行われないとするそうだ。こうすることで、将来解決するために保留できる。

totally orderedではない型はどうすればいいのだろうか。例えば、ポインターの値の大小比較は、同じ連続したストレージ上から確保されたメモリーでなければ意味をなさない。IEEE浮動小数点数にはNaNがあり、NaNの比較はかなり特殊な定義がなされている。

議論の結果として、

  1. 浮動小数点数型のメンバーが存在する場合、明示的なデフォルト演算子は生成は生成できない。
  2. ポインター型のメンバーが存在する場合、等価比較の明示的なデフォルト演算子は生成できる。
  3. ポインター型のメンバーが存在する場合、大小比較の明示的なデフォルト演算子は生成できない。

筆者が思うに、この決定はどうかと思う。結局この機能は、一般的な比較演算子を簡単に生成する機能なのだから、浮動小数点数やポインターといったありふれた型の存在によって、明示的なデフォルト演算子が使えないとあっては、とても使い道が制限される。

そういう責任はユーザー側にまかせて、コンパイラーはユーザーが指定したならば生成するべきであると思う。

N4115: Parameter Pack Searching

パラメーターパックの中に指定の型が含まれているかどうかを調べるtraits、is_contained_in<T, P>と、パラメーターパックが別のパラメーターパックの中の型をすべて含むかどうかを調べるcontains_type< T, P >の提案。

まず、パラメーターパックを格納するクラステンプレート、packer。

template <class... T> struct packer { };

packerはtupleに似ているが、tupleのようにメンバーを持たないので、必ずインスタンス化できる。

パラメーターパックPにT型が含まれているかを調べるis_contained_in traits。

template <class T, class... P> struct is_contained_in;

template <class T, class... P>
  constexpr bool is_contained_in_v = is_contained_in<T,P>::value;

その実装例

template <class T> struct is_contained_in<T> : false_type { };

template <class First, class... Rest>
  struct is_contained_in<First, First, Rest...> : true_type { };

template <class T, class First, class... Rest>
  struct is_contained_in<T, First, Rest...>
    : is_contained_in<T, Rest...> { };

あるpacker Tのパラメーターパックが、別のpacker Uのパラメーターパックをすべて含んでいるかどうかを調べるcontaines_type。

template <class T, class U> struct contains_types;

template <class T, class U>
  constexpr bool contains_types_v = contains_types<T,U>::value;

その実装例


template <class... TPack>
  struct contains_types<packer<TPack...>, packer<>> : true_type { };

template <class... TPack, class UFirst, class... URest>
  struct contains_types<packer<TPack...>, packer<UFirst, URest...>>
    : integral_constant<bool,
        is_contained_in<UFirst, TPack...>::value &&
        contains_types<packer<TPack...>, packer<URest...>>::value> { };

まあ、あっても困らない小粒なライブラリだ。

[PDFは消え去るべき] N4116: Nested Namespace Definition (rev 1)

名前空間のネスト。

namespace A { namespace B { namespace C {
} } }

という冗長なコードを、


namespace A::B::C {
}

と書けるようになる。

議論ではnamespaceのinlineや別名をどうするかという問題も出たそうだが、とりあえずそれは保留して、最小限の提案だけをする。つまりinline namespaceには対応しない。

別名の問題というのは、以下のようなコードが通らないということだ。

namespace A::C {} 
    using namespace B = A; 
    namespace B::C {} // ill-formed

ただし、もともと名前空間の別名は、このようには使えなかった。

namespace A { } 
    using namespace B = A; 
    namespace B { } // ill-formed

つまり、現状維持ということになる。

N4117: C++ Standard Library Active Issues List
N4118: C++ Standard Library Defect Report List
N4119: C++ Standard Library Closed Issues List

標準ライブラリに持ち上がっている問題、解決された問題、議論の結果問題ではないとされた問題の一覧。

N4120: Null Coalescing Conditional Operator

ある値を使う前に、値のnullチェックをするような処理は、プログラミングで頻出する処理である。これは、現在のC++14の文法では、条件式を使うことで実現できるが、条件式を使うのは色々とめんどくさい。

x ? x : y ;

という形になるのだが、xが長い複雑な式である場合、二度重複して書かなければならない。また、このままではxは二回評価されてしまうので、評価されることによる副作用を避けたいのであれば、まず一時オブジェクトに式を評価した結果を格納しなければならない。

auto && temp = x ;
temp ? temp : y ;

このため、他の言語にも存在し、またGCCの独自拡張で、Clangにも実装されている文法を、標準に提案する。この文法を使えば、以下のように書ける。


x ? : y ;

これは、以下のコードとほぼ同じ意味を持つ。

auto && temp = x ;
temp ? temp : y ;

違いは、tempはxと同じ値カテゴリーだとみなされるということだ。

なかなか小粒ではあるが便利であれば嬉しい提案だ。

N4121: Compile-Time String: std::string_literal<n>

Library Fundamentals TSに、std::string_literal<n>を追加する提案。

このライブラリは、コンパイル時の文字列を扱うためのライブラリである。リテラル型なのでコンパイル時に扱える。つまり、constexprオブジェクトにできるし、constexpr関数で使うこともできる。

コンパイル時に文字列を扱うクラステンプレートの実装方法としては、二つの方法がある。この論文で提案されているのは、

template < std::size_t n >
struct string_litera
{
    char data[n] ;
} ;

という形だ。文字数をテンプレート引数で受け取って、配列で文字列を持つ。これはリテラル型なので、コンパイル時に扱える。

もうひとつの実装方法としては、非型テンプレートパラメーターパックで文字を受け取ることだ。

tempalte < char ... data >
struct string_literal
{ } ;

まず、両方共、相互に簡単に変換可能である。

この二つの利点と欠点を考えると、パラメーターパックを使う実装は、文字列ごとに別のインスタンス化が必要で、文字列を名前マングリングで受け取り、りんカーを酷使する、スケールしない実装方法である。パラメーターパックによるコンパイル時文字列の実装、またテンプレートメタプログラミングは、たまたまチューリング完全であったために酷使された利用法であって、近代的なC++14では、constexprオブジェクトのほうが使いやすい。

静的リフレクションを入れるにも、何はともあれコンパイル時文字列は必要になるので、いずれ必要になるライブラリである。

ドワンゴ広告

今日は不自由なソフトウェアであるスーパーマリオランド2 6つの金貨で遊びながら通勤し、また昼休みにも6つの金貨で遊んだ。ドワンゴには路地裏と呼ばれているスペースがあり、ゲーム機が置かれているが、あまり遊ばれているのをみたことはない。なんでもぷよぷよ勢のために稼働するメガドライブなどが維持されているのだとか。

もちろん、今日はドワンゴ勤務中にこの記事を書いた。次の論文集は10月だが、明日からはBoost勉強会大阪の発表資料を作成するので、暇にはならない。

ところで、そろそろまたドワンゴのセミナールームを使ってC++勉強会を開くべきだろうか。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-03

2014-07-post-Rapperswilのレビュー: N4090-N4108

N4090からN4108までの論文は、問題集であったり、文面案だったりするので、特に詳細に解説することはない。概要だけ紹介する。

[概要すら解説する気が失せるPDF] N4090: The Maladies of All Member Templates: An Incomplete Biography of Specialization (DR727 + DR1755)

すでに上がっている問題に、DR727DR1755とがある。これは、現在の文面の解釈が曖昧なため、コンパイラーごとにテンプレートの明示的特殊化(論文では、わかりやすさのために完全特殊化という言葉を使っている)、部分的特殊化の挙動が微妙に違う。この問題を解決するためにIssaquah会議で話し合った結果、意見の一致するルールが浮かび上がってきたので、そのルールを記載し、テストコードを示す。合意したルールの挙動にするClangのパッチも書かれたようだ。

N4091: C++ Standard Core Language Active Issues
N4092: C++ Standard Core Language Defect Reports and Accepted Issues
N4093: C++ Standard Core Language Closed Issues

C++のコア言語で認知されている問題、解決済みの問題、議論の結果、問題ではないとされた問題の一覧。

N4094: Response To: Let return Be Explicit

N4074で、return {expr}はexplicitを無視することが提案された。つまり、以下のようなコードが合法になる。

struct X
{
    explicit X(int) { }
} ;

X f()
{
    // C++14では違法
    // N4094提案では合法
    return {0} ;
}

N4094は、この提案に反論する論文である。

反論に用いている例がいろいろある。この解説ではかなり簡略化して解説するが、論文ではすこし違う例を出している。例えば、chronoライブラリだ。std::chrono::secondsで、ある数値を秒、分、時の単位で解釈して秒数を返す関数を考える。

enum struct unit { seconds, minutes, hours } ;

std::chrono::seconds
convert( unit u, int count )
{
    switch( u )
    {
        case unit::seconds :
            return { u } ;
        case unit::minutes :
            return { u } ;
        case unit::hours :
            return hours{ u } ;
    }
}

このコードは、enumによって、数値の単位を切り替えて秒数を返す関数である。しかし、このコードにはバグがある。case unit::minutesのところで、return { u }としてしまっている。std::chrono::secondsのコンストラクターはexplicitなので、C++14では、このバグはコンパイル時に検出可能である。しかし、N4074提案では、このコードはコンパイルが通ってしまう。そして、実行時に期待しない結果をもたらすだろう。

C++11が発行されてから、まだ3年しかたっていない。return {expr}という形は、まだ今から挙動を変更してもいいほど使われていないのだろうか。

しかし、ネット上で検索しただけで、実際に使っている例が見つかる。実際に使われているものの挙動を変更するのは危険だ。

N4074の発端になったtupleだが、return { 1, 2 }に対して、std::tuple< std::chrono::seconds, std::chrono::nanoseconds >が戻り値の型の場合、どうするのか。

std::tuple< std::chrono::seconds, std::chrono::nanoseconds >
test1()
{
    // 危険、単位が不明
    return { 1, 2 } ;
}

std::tuple< std::chrono::seconds, std::chrono::nanoseconds >
test2()
{
    // 安全、単位が分かっているので正しく変換される。
    return { 1h, 2ms } ;
}

N3739では、Perfect Initializationと呼ばれる技法を利用したtuple側による対応により、test1は弾くがtest2は通すようにできる。これは理想的である。N4074提案は危険である。

そもそも、戻り値の型とreturn文とが、どちらも同じプログラマーの管理下にあるということは断定できない。

// 誰か他人が書いた関数
SafeData GetSomeData() ;

// 安全なデータは暗黙の型変換を許し
// 非安全なデータは明示的な型変換を要求するクラス
struct SomeClass
{
    SomeClass( SafeData ) ;
    explicit ( UnsafeData ) ;
} ;

SomeClass f()
{
    return { GetSomeData() }  ;
}

この場合、GetSomeDataという関数の戻り値の型が変えられたとすると、C++14ならばコンパイルエラーになってくれるが、N4074提案では、コンパイルが通ってしまう。そして、実行時に期待しない結果をもたらすだろう。

スマートポインターと組み合わせるのも危険だ。そもそも、C++14では、newなど使わない。make_uniqueやmake_sharedを使う。そういうレガシーなコードとのやりとりのために、型安全性を犠牲にしてよいわけがない。

そもそも、{}を使うと暗黙に型変換するというのは、一貫性に欠ける。リスト初期化では、narrowing conversionを禁止しているではないか。

char f()
{
    int x = 0 ;
    return { x } ; // ill-formed、intからcharへのnarrowing conversion
}

このように、{}を使う文法で暗黙の型変換に制限しておきながら、他方で緩めるとは一貫性に欠ける。

論文ではこれらの例を挙げて、N4074提案に反対している。

N4095: File System TS Active Issues List (Revision R2)
N4096: File System TS Closed Issues List (Revision R2)
N4097: File System TS Defect Report List (Revision R2)

File System TSに持ち上がっている問題、議論の結果問題ではないとされた問題、解決された問題の一覧。

N4098: File System TS Editor's Report

File System TSの編集者による最新のドラフト、N4099に対する変更報告書。

N4099: Draft Filesystem Technical Specification

File System TSの現時点での最新のドラフト文面。

File Systemは、ファイルシステム、つまりファイルやディレクトリなどの列挙や操作を行うライブラリである。

[PDF注意] N4100: Programming Languages — C++ — File System Technical Specification

File System TS。

N4101: C++ Standard Evolution Active Issues List
N4102: C++ Standard Evolution Completed Issues List
N4103: C++ Standard Evolution Closed Issues List

C++の新機能に持ち上がっている問題、解決済みの問題、議論の結果問題ではないとされた問題の一覧。

N4104: Technical Specification for C++ Extensions for Parallelism, Working Draft,

STLのアルゴリズムに並列実行版を追加するParallelism TSのドラフト。

[PDF注意] N4105: Information technology – Programming languages, their environments and system software interfaces – Technical Specification for C++ Extensions for Parallelism

Parallelism TS。

N4106: Parallelism TS Editor's Report, post-Rapperswil

Parallelism TSの編集者による変更報告書。

N4107: Technical Specification for C++ Extensions for Concurrency, Working Draft

std::futureの機能を拡張するConcurrency TSのドラフト。

N4108: Concurrency TS Editor's Report, February 2014

Concurrency TSの編集者による変更報告書。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

今日は、社内でビブリオスというドラフトゲームを行った。社内にビブリオスをインストできる人間がいたので助かった。このビブリオスはドワンゴの社内同好会であるボドゲ部の部費で購入したそうだ。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

2014-09-01

2014-07 post Rapperswil mailingのレビュー: N4080-N4089

またもやC++論文のレビュー10本、今回は全く新しい提案ではなく、議論された挙句の文面案が多いので、あまり書くことがない。

N4080: File System Immediate Issues

filesystemライブラリのTS案に持ち上がっている問題のうち、Rapperswil会議で対応が決定されたもの一覧。

N4081: C++ Extensions for Library Fundamentals, Working Draft

標準ライブラリに対する拡張のTSのドラフト。

N4082: Programming Languages — C++ Extensions for Library Fundamentals

N4081のPDF版?

N4083: Editor's Report — Library Fundamentals TS V1

ライブラリ拡張TSの編集者による編集報告書。

N4084: C++ Extensions for Library Fundamentals, Version 2, Working Draft

ライブラリ拡張TSのバージョン2。変更は軽微の模様。

N4085: Editor's Report — Library Fundamentals TS V2

ライブラリ拡張TSのバージョン2に対する編集者の変更報告書。

N4086: Removing trigraphs??!

規格文面からトライグラフを除去し、Annex C(deprecatedな機能)に移す提案。

トライグラフはまともに使われていなかった。alternative tokensはいまだに残っているので注意。

N4087: Multidimensional bounds, index and array_view, revision 3

Viewable(ストレージのサイズと専用へのポインターを返すsizeとdataメンバー関数を持つ型)から、見かけ上多次元配列を作り出すラッパーライブラリの文面案。

[PDF注意] N4088: Task Region R3

strict fork-join task parallelismというパラダイムによる並列実行ライブラリ、task regionの提案。

task regionはMicrosoftのPPLやIntelのTBBを土台に設計されている。また、IntelのC++拡張機能であるClik Plusからも影響を受けている。

[危険PDF] N4089: Safe conversions in unique_ptr<T[]>, revision 2

unique_ptr<T[]>で、constを付け足すような安全なポインターの型変換を許可する変更。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

この記事は、ドワンゴに12時に出勤して、2時間執筆し、2時間昼寝して、1時間執筆して書き上げた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0