2014-11-26

2014-10-pre-UrbanaのC++標準化委員会文書のレビュー: N4210-N4219

[またPDF] N4210: IBM comment on preparing for a Trigraph-adverse future in C++17

トライグラフ除去の可否を問う投票で、賛成票が多数派であったが、その中でIBMは強く反対の立場であった。トライグラフは現に利用者がいて、ASCIIが多数派なC++では、マイノリティといえど、実際の利用者がいる機能を除去するのは、C++のためにならないという主張である。そこでBjarne Stroustrupから、その現実の利用者をまとめて報告せよと言われたので、IBMはこの文書を提出した。

IBMによれば、機密のため顧客名は明かせないものの、現実に利用者が多数いるという。特に重要な利用者の、ある北アメリカの大手の某銀行は、IBMメインフレームを使い続けている。処理は極めて重要である。彼らは長年の多大な労力をデバッグにつぎ込んでおり、安定していて、他の環境に移行することは決してない。

C++はASCIIが多数派であるが、物言わぬマイノリティの声を代弁するためにもIBMはトライグラフ除去に反対するという。

どうも私にはよくわからない世界だ。

N4211: File System TS Active Issues List (Revision R3)
N4212: File System TS Closed Issues List (Revision R3)
N4213: File System TS Defect Report List (Revision R3)

TSに提案されているFile systemライブラリに持ち上がっている問題集

[PDF] N4214: A Module System for C++ (Revision 2)

40年前のコピペ技術である#inludeにかわるモジュール機能の提案。

現在、C++が使っているコンパイルとリンクモデルは、C言語から受け継いだものである。各翻訳単位は、他の翻訳単位のことを一切知らずに処理される。翻訳単位を超えるには、名前リンケージ(シンボル名)を使う。名前に対応する定義はひとつだけでなければならない(ODR)。

翻訳単位の外に見える名前である外部名を、手動によるコピペで管理することを防ぐために、我々は40年前の技術である自動コピペ技術、プリプロセッサーによるヘッダーファイルを未だに使っている。これは、コンパイラーからみれば、コピペである。

ヘッダーファイルの中身はマクロによって書き換わってしまうおそれがあり、誤りの元である。

変数や関数の宣言ぐらいしか書かれていなかった昔のC言語ならば、まだ十分に耐えられたのだが、モダンなC++では、ヘッダーファイルには実行されるコードが記述されている。コンパイラーは翻訳単位ごとに重複した内容を処理しなければならず、現代のC++のコンパイル時間の増大の原因になっている。

この論文では、C++に近代的なコンポーネント化をもたらすための機能、モジュールの設計を考案している。

モジュールは、既存のプリプロセッサーと組み合わせて使うことができる。これは穏やかな移行のために必要である。既存のプログラムはプリプロセッサーによる自動コピペ技術でコンポーネント化されていて、自動的にモジュールに書き換わってはくれない。今後もしばらくはぷりプロセッサーを使う必要がある。この地上からプリプロセッサーを一掃して、純粋なモジュールのみを使う素晴らしき新世界になるまでは。

さて、モジュールの使い方だが、論文では以下のような文法を想定している。

import std.vector ; // #include <vector>

int main()
{
    std::vector<int> v ;
}

モジュールを使う側からすれば、#includeの機械的な置換ぐらいで対応可能だ。

では、ライブラリ側がモジュールに対応するにはどうすればいいのだろうか。これには二つの設計が考えられる。コンパイラー側が自動的に対応する方法と、ソースファイル中に宣言を記述するものだ。論文では、コンパイラーに推測させるよりも、明示的な宣言の方が好ましいとしている。

モジュールは、モジュール宣言で宣言する。

module module-name ;

この宣言の意味は、この翻訳単位のこれ以降の宣言は、module-nameという名前のモジュールの一部であるという宣言だ。モジュール宣言は翻訳単位に一つしか存在できない。前回の論文では、翻訳単位の最初の宣言でなければならないとしていたが、今回の論文では、そのような制約は必要ないということで緩和された。移行を簡単にするために、module宣言の前に宣言されているものは、グローバルモジュールに属するものになる。

モジュールによるインターフェース宣言は、実装と同じソースファイルに記述することもできるし、別のソースファイルに記述することもできる。これは、どちらか片方だけに制限するのは現実的ではないためだ。

モジュールは外部へのテントリーポイントを、export宣言で宣言する。

export toplevel-declaration
struct X { } ;
module M ;
struct Y { } ;

export struct Z { } ;

このように記述した場合、Xはグローバルモジュールに属する。YはモジュールMに属するが、エクスポートされていない。ZはモジュールMに属するexportされている名前だ。

toplevel-declarationは、import宣言かmodule宣言か、通常の宣言のいずれかである。export宣言にimport宣言を指定した場合、import宣言で指定したモジュールがエクスポートする宣言が、現在のモジュールから見えることになる。

module lib ;
export import std.vector ;

export namespace lib {

class Foo
{
    std::vector<int> v ;
} ;

}

export宣言でmodule宣言を指定すると、現在のモジュールのサブモジュールの名前を宣言したことになる。

export module module-name ;

export宣言でトップレベルの宣言をすると、その宣言は外部に公開される。


module foo

// barはfooをimportしてもimportされない
import bar ;

// fooをimportするとhogeもimportされる
export import hoge ;

// 内部
void f( int ) ;

// 外部に公開される
export void f( int, int ) ;

// 外部に公開される
export namespace foo {
    void g() ;
}

まだまだ文法は変わるだろうし、具体的な実装が出てきてからが面白いところだろう。

[読みにくいPDF] N4215: Towards Implementation and Use of memory_order_consume

memory_order_consumeの存在意義の解説。特にロックやアトミックのような命令を発行することなく、コンパイラーの一部の最適化を抑制するだけで実装可能だが、なかなかそういう高品質の実装が出てこない現状について書いている。

N4216: Out-of-Thin-Air Execution is Vacuous

虚空から現れいでたる(Out Of Thin Air)値とは、何の脈略も関連性もなく現れる値のことである。例えば、何らの排他的制御も同期処理も行わずに同じオブジェクトに複数のスレッドから値を書き込んだ場合、その結果の値は未定義となる。未定義である以上、結果の値がどのようになってもかまわない。しかしまったく脈絡のない値が出てきてもいいものか。

議論の結果、規格では実装に対しOOTAを避けるように記述されているが、具体的なOOTAについて記述されていない。この論文では、具体的なOOTAについて記述している。

N4217: std::rand replacement, revision 1

std::randを廃止したい。しかし、std::randはあまりにもお手軽に使うことができる。

1から6までの乱数をひとつ生成したいとする。大昔のrandならば、以下のように書ける。

// このようなコードを書いてはならない。
int main()
{
    std::srand( std::time( nullptr ) ) ;
    int pip = std::rand()%6 + 1 ;
}

まずstd::time()で初期化するのはお勧めできないし、剰余を使う方法は、均一に分布しない。

では、C++11ではどう書けばよいのか。

// 長い
int main()
{
    std::random_device d ;
    std::seed_seq s = { d(),d(),d(),d(),d(),d(),d(),d(),d(),d() } ;
    std::mt19937 engine( s )  ;
    
    std::uniform_int_distribution dist( 1, 6 ) ;

    int pip = dist( engine ) ;
}

このコードは毎回初期化されるし、均一に分布するが、やや長過ぎる。C++11の乱数ライブラリをまともに使うには、乱数エンジンやら乱数分布やらの概念を学ばなければならない。

std::randのような手軽さを持っていて、モダンな小さい乱数ライブラリが欲しい。そこでこの論文では、そういうライブラリを提案している。


int main()
{
    // プログラムの実行ごとに別の状態に初期化される
    std::seed_init() ;

    int pip = std:randint( 1, 6 ) ;
}

結局、手軽でなければ普及しないのであれば、入るべき提案だろう。

[PDF] N4218: Variant: a typesafe union

型安全なunionであるvariantライブラリの提案。Boost.Variantに似たインターフェースになっている。ビジターなし、Variantを再帰的に使った場合の特殊な対応なし。

int main()
{
    std::experimental::variant< int, double > v ;
    v = 0 ;
    v = 0.0 ;

    int value = std::experimental::get<double>(v) ;
}

テンプレート仮引数に指定した型のみ入れられる型安全なunionだ。

論文で言及しているが、variantは空の状態になりうるという。

variant< T, U > a = T() ;
variant< T, U > b = U() ;

a = b ; // 空になりうる

この場合、aにbを代入するために、まずaに入っているTのオブジェクトを破棄して、次にUを構築するが、Uの構築に失敗した場合は、空の状態になる。

N4219: Fixing the specification of universal-character-names (rev. 2)

universal-character-nameに対する実装の挙動の提案。

ドワンゴ広告

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

最近、街コロというボードゲームに興味を持っているが、いまいち戦略が見いだせない。

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

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

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

1 comment:

Anonymous said...

トライグラフには死んで欲しいです。たとえ日本語使用者である自分であってもです。3という数字が今のコンピュータには不吉すぎます。
モジュールは面白いですね。今後constexprが普及してコンパイル速度が激遅になる可能性を加味して、入ってほしい機能です。自分はヘッダー書くの苦手ですし。
ファイルシステムは難航してますね。入ってほしいです。
Randのようなお手軽関数はまぁ毎度宣言を書くよりはお手軽なので入ってほしいです。注文を付けるならエンジンを選べるようにしてほしいです。ゲームでは一様乱数が必ずもベストではないことがあるんですよ。あー、でもそうすると初期化コストであんまり記述量変わらないですねぇ・・・。Orz
バリアントかタイプイレーザーが入ってほしいと思います。個人的な夢としてオレオレ言語を作りたいと思っています。その時の変数システムのサイズを減らしたいのでどうしても言語機能として備わってほしい機能に思います。元々ベーシック畑で育ったのでそっちの方が肌に合うというのもありますが。しかししばらく触ってないので思ってるだけかもしれません。