2013-09-08

2013-09 pre-Chicago mailingの簡易レビュー

2013-09 pre-Chicago mailingが公開された。

ところで、私がPDFを目の敵にしているのは、様々な理由がある。まず、PDFはプレインテキストと違い、複雑なパースが必要であり、扱いにくい。文書はプレインテキスト、あるいはプレインテキスト上で簡単なタグを用いたフォーマットで書くべきである。プレインテキストであれば、あまり文字列処理に優秀ではないプログラマーや、簡易的なプログラミング言語やツールでもパースが容易であり、人間にも機械にも処理しやすい。たとえ、文書のフォーマットが文法的に曖昧であったとしても、扱いやすいという点でプレインテキストのほうがよっぽど優れている。たとえ文法的にはきっちりしていたとしても、プレインテキストとしては解釈できず、そのパーサーの実装には何人もの優れたプログラマーの一年以上の労働力が必要な複雑なフォーマットは、プレインテキストよりも扱いづらい。また、PDFは立派なプログラミング言語と認めていいほど複雑である。作者の勝手気ままで読みにくいレイアウトを強制される。多くのPDFにはフォントが埋め込まれており、独善的で読みにくいフォントを強制されることが多い。また、この記事を書く上での問題は、プレインテキストやHTMLならば、お手製のbookmarkletを使ってa要素を生成できるが、PDFの場合は、a要素を手で書かなければならない。

そもそも、PDFは高度なフォントの描画方法がプロプライエタリで、解析されて詳細が公にされるまで、Adobeは自社の優位を保つために仕様を非公開にしていたという邪悪な歴史もある。

PDFはレイアウトをきっちり決めて印刷をするという用途には、僅かな利点があるかもしれない可能性が無きしもあらずといったことを前にどこかの文章で読んだ気がする。しかし、所詮はそれだけだ。利点がもし仮にあるとしても印刷だけだ。印刷のみを目的とする以外のドキュメントは、PDFを用いてはならない。

PDFは人類の善のために廃止されなければならない。私は表現規制とPDFが嫌いだ。

N3706: C++ Distributed Counters

複数のスレッドから安全にインクリメントできるカウンターライブラリの提案。このカウンターは、複数のスレッドから効率良くインクリメント/デクリメントできるかわりに、値を取得する際には、コストがかかる。インクリメントは大量に発生するが、値を読み出すのはたまにしか行われないという状況に適したパフォーマンス設計になっている。

使い方は、++演算子や--演算子でカウンターを操作し、loadメンバー関数で値を取得する、まあ一目見れば分かるものとなっている。

counter::simplex<int> counter ;
counter++ ;
counter-- ;
int c = counter.load() ;

[PDFバカ] N3708: A proposal to add coroutines to the C++ standard library

C++にコルーチンを追加する提案。この論文は、具体的な機能の提案ではなく、コルーチンの説明や、既存のC++における実装例を紹介するものとなっている。実装例としては、主にBoost.Coroutineが詳しく取り上げられている。

N3710: Specifying the absence of "out of thin air" results (LWG2265)

アトミック操作において、memory_order_relaxedであっても、「全く脈絡のない結果」が現れることを禁止する変更。これは、一部のアーキテクチャにコストがかかるが、論文では必要な変更であると主張している。

全く脈絡のない結果("out of thin air" results)とは何か。現在の規格の文面では、weakly orderedなアトミック操作では、ロードが競合するストアを結果を見ることも見ないことも許されている。この定義は、とても不思議な挙動、全く脈絡のない結果が現れることを許している。

今、アトミックオブジェクトx, yが0で初期化されていて、r1, r2はアトミックではない通常のオブジェクトだとする。

スレッド1:
  r1 = x.load(memory_order_relaxed);
  y.store(r1, memory_order_relaxed);

スレッド2:
  r2 = y.load(memory_order_relaxed);
  x.store(r2, memory_order_relaxed);

スレッド1は、xをyにコピーする。スレッド2は、yをxにコピーする。現行の規格の定義では、xとyに関しては、それぞれのスレッドがお互いに他のスレッドのストア結果を見るか、あるいは初期値0をみるかという挙動になる。

意外なことに現行の規格の定義では、非アトミックオブジェクトであるr1とr2の値は、生命、宇宙、そして万物についての究極の疑問の答えである42になる。もしくは、その他の全く脈絡のない値になる。

これはなぜかというと、スレッドのロードが、お互いに他のスレッドのストア結果を参照し、その上で、コンパイラーや実行環境が、結果をあらかじめ予測して、実際のストア前に値をつくりだし、その後、実際の実行で予測を追認するということがあるからだ。

ただし、既存の実装で、このようなコードで「全く脈絡のない結果」が現れるものはない。ではそのような挙動は文面で禁止すればいいのではないかというと、この挙動だけは禁止するが、他の有用なコンパイラーやハードウェアにおける最適化や最も効率のいい挙動を阻害しない定義が、とても難しいからだ。

たとえば、以下のコードは、一部の既存のアーキテクチャーでは、x == y == 42となる。

スレッド1:
  r1 = x.load(memory_order_relaxed);
  y.store(r1, memory_order_relaxed);

スレッド2:
  r2 = y.load(memory_order_relaxed);
  x.store(42, memory_order_relaxed);

アトミック操作に不慣れな読者は、「しかし、スレッド2では、yのロードの後にxのストアを実行しているではないか」と言いたくなるかもしれない。C++のアトミック操作では、単一のアトミックオブジェクトの操作のアトミック性が保証されるだけで、複数のオブジェクト間のアトミック性は何も保証されない。また、xのストア操作は、yのロード操作に一切依存していないので、コンパイラーやハードウェアは、yのロードが行われる前にxのストアを実行しても、規格準拠である。複数のオブジェクト艦でのアトミック性を保証したければ、複数の操作を排他的なロックでガードしなければならない。あるいは、今提案されているTransactional Memoryも使える。

x86のような何もしなくてもそれなりに保証されているようなアーキテクチャでは実感しにくいが、世の中にはそれほど慈悲深くはないアーキテクチャもあるということだ。もちろん、その結果として、x86にはない利点もあるのだが。

この問題は今に始まった話ではなく、たとえばJavaでは10年ほど延々と議論された挙句、いまだに解決されていない問題だったりする。C++では、memory orderを細分化したことにより、この問題の影響を受けるのは, memory_order_relaxedと、memory_order_consumeの一部の利用方法だけだという。まず一般人は使わない機能だ。

この問題について色々と議論した結果、memory_order_relaxedにおいても、このような「全く脈絡のない結果」を禁止することが望ましいという意見が形成されていった。その場合、memory_order_relaxedの実装は、一部のアーキテクチャでは、多少のコストがかかる。そのアーキテクチャの例としては、ARMやnVidiaが挙げられている。

「全く脈絡のない結果」を禁止する意義

わざわざ弱いmemory orderを使うのだから、全く脈絡のない結果が現れたとしても自己責任だとして済ませてはダメなのか。

全く脈絡がないといっても、実際には、何らかの影響は受ける。その影響は、ひょっとしたらプログラム外部の影響かもしれない。

たとえば、Javaでは、信頼できないコードを安全に実行する機能を提供している。信頼できないコードが「全く脈絡のない結果」を引き出すコードを延々と実行し続けた結果、低確率だがユーザーの秘密のパスワードを引き当てることができるとしたら、そのような規格は信用できない。

C++はそのような利用例を想定していないとはいえ、やはり全く脈絡のない結果が現れるというのは現実でも、規格の上でも、あまり褒められたものではないし、常識にも反する。

というわけで、「全く脈絡のない結果」を禁止する提案。この提案が受け付けられた場合、先に上げたふたつのコード例(両方とも)で、生命、宇宙、そして万物についての究極の疑問の答えが出てくることはなくなる。これはつまり、一部のアーキテクチャに、memory_order_relaxedであっても、わずかなコストを強いることになる。

[PDFアホ] N3711: Task Groups As a Lower Level C++ Library Solution To Fork-Join Parallelism

C++で並列実行をサポートする提案が次々に挙がっている。たとえば、STLのアルゴリズムに並列実行版を追加する提案もある。しかし、並列実行を容易に記述できる低級な機能のサポートという点では、意見が分かれている。

新たなキーワードを追加することにより、コア言語で低級な並列実行をサポートする提案があった。これは、Intelの実装例を元にしている。ところで、コア言語ではなくライブラリでサポートするとどのようになるのかという疑問があったため、PPLを開発したMicrosoftや、TBBを開発したIntelにより、ライブラリによる低級な並列実行のサポート、task_groupのコンセプトが示された。まあ、小さくまとまっていて悪くはない設計だ。

PPL/TBBとの違いとして、実行の中止をサポートしないというのが興味深い。ライブラリによる一括した実行の中止機能は実装を複雑にし、またアンチパターンであり問題も多い。中止機能は、ユーザー側で、協調的な中止機能として実装されるべきであるとしている。

[PDFマヌケ] N3712: Policy-Based Design for Safe Destruction in Concurrent Containers

複数のスレッドから同時にアクセス、変更できるコンテナーがほしい。そのようなコンテナーを実装するには、ロックフリーな実装方法が多数ある。どれも一長一短であらゆる利用例に適用できる方法はない。そのため、実装方法をポリシーベースで指定できる必要がある。ただし、利用は実装方法の如何にかかわらず同じであるべきだ。

そこで、そのような共通のインターフェースとしてreclaimerを提案し、共通の最小限の保証を定めたい。この論文では、そのようなインターフェース案を設計して、それが真に多数の実装方法の効率良く隠匿するのに十分であるかどうかを問うている。このインターフェースは、concurrent_ptrとguard_ptrにより成り立っている。なにか操作をしたい時は、guard_ptrを取得する。guard_ptrが存在する限り、オブジェクトは勝手に消されたり変更されたりしない。

これはshared_ptr/weak_ptrに似ている。というより、流用することもできるのだが、shared_ptr/weak_ptrでは保証が強すぎるため、もっと弱い保証のインターフェースを新たに作ることにした。

N3716: A printf-like Interface for the Streams Library (revision 1)

printfのようなフォーマットを指定してstreamに出力できるstd::putfの提案。以下のように使う。

std::cout << std::putf("%s %d", "hello", 123) ;

Boost.Formatのような、 positionalな指定もできる。

std::putf("%2% %1%", 1 , 2 ) ; // 2, 1

私はそもそもprintfのフォーマットがあまり好きではないし、文字列処理は入出力とは明確に切り離されているべきだと思うので、この提案は嫌いだ。streamライブラリとは独立した文字列フォーマットライブラリとして設計すべきだ。

[PDFうつけ] N3718: Transactional Memory Support for C++

Transactional Memoryの解説論文。なかなかわかりやすく書けているのだが、惜しむらくはPDFであるということだ。PDFなので非常に読みにくい。また、こともあろうか、物理的なページを水平にニ分割して、ソースコード例を真っ二つにして左右に論理的に配置しているのは、狂気の沙汰だ。

Transactional Memoryというのは、コア言語のキーワード付きの複合文として取り入れられる。

transaction_atomic noexcept { body }
transaction_atomic commit_on_escape { body }
transaction_atomic cancel_on_escape { body }

キーワード、transactional_atomicに続き、例外時の挙動を指定するキーワード(それぞれ、無例外、コミット、キャンセル)、そして複合文。このような文を、Atomic transaction statementと呼ぶ。

atomic transaction statementが実行されている間は、他のスレッドからは、途中のオブジェクトの変更課程は見えない。文の実行後は、あたかもatomic transaction statementの中で行われたオブジェクトに対する変更が他のスレッドからは、一括して行われたように見えるようになる。

このような挙動は、mutexによる排他的なロックを協調的に使うことでも実現できる。ただし、mutexによるロックは正しく使うのが難しい。状況によっては、本当に難しくなる。たとえば、リカーシブではないmutexを同一スレッドで何度もロックしようとしてしまったり、複数のmutexをロックする状況で、まとめて一括std::lockが使いづらい場合などだ。これは、ユーザーとライブラリが両方ともmutexを使っている場合、とくに問題になる。このような問題に対処しようとすると、ソースコードがとても汚くなる。

Transactional Memoryは、そのような場合でも、綺麗なソースコードで記述することができる。また、ソースコード中でmutexのような明示的なオブジェクトを管理したりする必要がない。

Transactional Memoryは、transaction safeという概念も導入している。これは、Transactional Memoryというのは、競合を検出した場合、それまでの処理を打ち捨てて、また初めから実行し直すという実装を念頭に置いている。そのため、やり直せないような処理、たとえばI/Oだとかは、Transaction unsafeである。atomic transaction statementには、transaction unsafeなコードは記述できない。また、関数にも、transaction safeかunsafeかを、キーワードで指定することができる。関数はデフォルトでtransaction safeとなる。ただし、関数の定義にunsafeなコードがある場合は、unsafeとなる。関数の宣言だけ提供されている場合は、safeだとみなされる。実際の安全性は、リンク時に判断される。

transaction unsafeなコードも、似たような挙動で実行したいという需要のために、relaxed transaction statementが用意されている。

transaction_relaxed { body }

これはtransaction unsafeなコードでも実行できる。relaxed transaction statementは、実行前にプログラム中で共通の特別なmutexをロックし、実行後に解放したような挙動になる。

transaction unsafeの判定方法が、論文の方法ではよくわからない。printfなどはどのようにしてunsafeだと判断されるのだろうか。printfのような標準ライブラリはunsafeだと明示的に指定できるとして、既存のライブラリはどうするつもりなのか。

N3719: Extend INVOKE to support types convertible to target class

std::functionやstd::bindやその他のライブラリなどで使われているINVOKEの仕様を改良する。

INVOKEは、メンバー関数ポインターに対して使われた場合、実引数をクラスのオブジェクトだとみなして、そのオブジェクトとメンバー関数ポインターで関数呼び出しをする。問題は、実引数の型が、メンバー関数ポインターのクラスのオブジェクトに変換可能な型の場合、今の規格のINVOKEの定義では動かない。しかし、動いて欲しい例はたくさんあるので、その場合に暗黙の型変換を許容する。

N3720: Working Draft Technical Specification - URI

またまた新たなURI処理用のライブラリ。

[PDFボケ] N3721: Improvements to std::future<T> and Related APIs

std::futureを非同期処理にまともに使えるようにするため大幅に改良する提案。すでに紹介した昔の論文、N3625の改訂版。ただしタイトルが変わっている。

[PDFクソ] N3722: Resumable Functions

タイトル通り、resume可能な関数の提案。これも目的は非同期処理のため。N3721と対になっている。N3721はライブラリベースのアプローチだが、こちらはコア言語ベースのアプローチとなっている。

Extend operator-> to support rvalues.

operator ->のオーバーロードは、クラス型か、クラスへのポインター型を返さなければならない。クラス型を返した場合は、そのクラスのoperator ->が呼び出される。最終的には、クラスへのポインター型が返され、そのポインターに対して組み込みのoperator ->が、オペランドとともに呼ばれる。

ここでの問題は、最終的にポインターを返すという都合上、rvaluesを返すことができないという事だ。例えば、以下のようなコードを書くことはできない。

class Foo
{
    int member ;
    Foo( int value ) : member(value) { } 
} ;

class Bar
{
    Foo * operator ->()
    {
        return &Foo(123) ;
    }
} ;

なぜならば、prvalueのアドレスは関数から戻った時点で無効になるからだ。ここで、ポインターではなくクラスを返すことが出来れば、prvalueを返すこともできる。

そこで、そのようなオーバーロードを付け加えてはどうかという提案。具体的には、それ自体には意味のないタグとしてのint型の仮引数を取るオーバーロードとなる。

class Bar
{
    Foo operator ->( int )
    {
        return Foo() ; // OK
    }
} ;

[PDFウンコ] N3724: A Parallel Algorithms Library

従来のSTLのアルゴリズムに並列実行版を追加し、またポリシーで指定できるようにする提案。

[PDFカス] N3725: Original Draft Specification of Transactional Language Constructs for C++ Version 1.1 (February 3, 2012)

IBMとIntelとOracleによる初期のTransactional Memoryのドラフト文面。これは今の文面とはだいぶ変わっている、N3718でも言及されていたように、Transaction expressionなどもみられる。transaction expressionは、単にatomic transaction statemmentを含むlambda expressionをそのまま実行すればいいだろうということで廃止された。

[PDFドアホ] N3726: Polymorphic Memory Resources

従来、コンパイル時ポリモーフィズムのみのアロケーターに、実行時ポリモーフィズムを可能にする変更。アロケーターは、実行時の状況に合わせて変更したいということがよくある。そのためには、現在のテンプレート実引数として渡すコンパイル時ポリモーフィズムは使いづらい。第一、オブジェクトの型がアロケーターによって変わるのは使いづらい。

そのため、標準ライブラリでアロケーター共通の基本クラスを提供し、また一部のライブラリの実装も変え、実行時ポリモーフィズムを実現できるようにする。

この提案はC++14ではなく、TRとして規格とは別に出る。そのため、C++14には入らない。

N3227: A proposal to add invoke function template

std::functionやstd::bindなどで使われているINVOKEの仕様を満たす関数テンプレートinvokeを標準ライブラリに追加する提案。INVOKEを正しく実装するのは、高度なテンプレートメタプログラミングの知識が必要になるし、車輪の再発明でもあるので、これは当然、標準ライブラリで提供されているべき機能だ。C++11にはなかったのが不思議なぐらいだ。

N3728: Packaging Parameter Packs (Rev. 2)

本の虫: 2012-09 pre-Portland mailingのあまり簡易ではないレビューでも解説した、N3416の改訂版。パラメーターパックリテラルを追加するというもの。シカゴ会議までには実験的な実装を用意できるかもということだ。

N3729: Invocation type traits

これも少し前に解説した論文N3579の改訂版だ。あるクラス型で、operator ()を、与えた型を実引数として呼んだ場合、実際に呼ばれる関数シグネチャはどのようなものかということを返すメタ関数、std::invocation_typeの提案。

struct S
{
    void operator ()( int, int ) ;
    void operator ()( double, double ) ;
} ;

// typeの型はvoid(double,double)
using type = std::invocation_type< S( double, double ) >::type ;

S型のオブジェクトに対して、double, doubleという実引数型でoperator ()を呼び出すと、void S::operator()(double,double)の方がオーバーロード解決により呼び出される。この場合、invocation_typeのネストされた型名typeは、void(double,double)になる。

template argument deductionやoverload resolutionの結果を返すメタ関数とみることもできる。

とても便利なメタ関数だ。

N3730: Specialization and namespaces

これは新顔。テンプレートの特殊化を、元のテンプレートの名前空間以外のスコープで行えるようにする提案。

namespace A
{
template < typename T > ;
struct S ;
}

namespace B
{
template <>
struct ::A::S ;
}

動機としては、わざわざ特殊化を書くためだけに、深い名前空間スコープを一度閉じたくないということがある。たとえば、std::hashはユーザー側が特殊化を追加することを想定している。もし、ユーザーの定義するクラスが深い名前空間スコープの中にある場合、一度閉じなければならない。

namespace deep_namespace { namespace further_deep_namespace { namespace extra_deep_for_precaution
// 独自のクラス
struct Number { /*実装*/ } ;
} } } // ここで一旦閉じる。

// std::hashの特殊化を書く
namespace std
{
template <>
struct hash< deep_namespace::further_deep_namespace::extra_deep_for_precaution::Number >
{
    std::size_t operator()( deep_namespace::further_deep_namespace::extra_deep_for_precaution::Number const & n ) { /* ... */ }
} ;
}

namespace deep_namespace { namespace further_deep_namespace { namespace extra_deep_for_precaution
// 続きを書く
} } }

以下のようにかけたらなんとすばらしいことか。

namespace deep_namespace { namespace further_deep_namespace { namespace extra_deep_for_precaution
// 独自のクラス
struct Number { /*実装*/ } ;

template <>
struct ::std::hash< Number >
{
    std::size_t operator()( Number const & n ) { /* ... */ }
} ;

} } }

[PDFヴァカ] N3731: Executors and schedulers, revision 2

小さな作業量の処理を渡して実行する、いわゆるスレッドプールを抽象化した標準ライブラリの提案。

[PDFヴォケ] N3732: Value-Oriented Concurrent Unordered Containers

前回の論文N3425で、STLのコンテナにできる限り近づけたインターフェースのConcurrent Unorderedコンテナを設計したところ、ポインターを返すインターフェースは一部の実装方法の妨げになり、また不適切な誤用を招きやすいという意見が噴出した。そのため、既存のSTLコンテナのインターフェースには似せない、TBBやPPLに似た、全く新しいvalue-orientedなインターフェースを設計してみた。

[連番でPDFが続くたびにレビューする意欲が失われていく] N3733: ISO/IEC CD 14882, C++ 2014, National Body Comments

CDに対するNational Bodyからのコメント一覧。今回、日本からは特に送っていない。特に個人的に興味深いものを挙げると。

多くの国:未解決のactive issuesを解決しろ。

スイス:C++14はバグフィクスリリースなのだから、新機能の追加はするな。C++11の挙動を変更するな。ライブラリの挙動も同様だ。

オランダ、アメリカ合衆国、スペイン:digit separatorを入れろ。

digit separatorは個人的に欲しかった。

大ブリテン及び北アイルランド連合王国:2.14.5p8で、UTF-8文字列リテラルの型はconst charの配列って書いてあるけど、この定義に従うと、例えばu8"À"、つまりu8"\u00c0"の型は、const char[3]で、その値は{ 0xc3, 0x80, 0 }で初期化されることになる。でも、charって0x80を表現可能だと保証されてないよね。どうすんのよこれ。

これは興味深い。確かに、charは0x80を表現可能だとは保証されていない。定義上、charはかなり曖昧になっている。charは符号付きかどうか規定されていない。charとsigned charとunsignedは異なる型として認識される。

問題は、多くのC++の実装では、charとは符号付き8bit整数である。8bitということは、256通りの状態しか表現できない。例えば私の環境のGNU/Linux x86-64のGCCの実装では、-128から127(あるいは-0x80から0x7Fまで)までの間の整数を表現可能だ。つまり、私の環境のC++実装では、charは0x80を表現できない。これはつまり、charはUTF-8文字を、表現できないということになる。

だから私が2011年に主張したように、UTF-8の1単位を表現する型には独自の型が与えられるべきだったのだ。それを「charはバイナリを表現する型として歴史がある」とかなんとかで無視してcharで押し通した挙句がこのザマだ。ツケは支払わなければならないが、綺麗に解決はできないだろう。

現実的には、多くのC++実装で、charは符号付きなのだが、そのC++実装を使うコードはその事実を無視している。

大ブリテン及び北アイルランド連合王国の提案としては、型をcharからunsigned charに変えるか、あるいはcharが0x80を表現可能なことを保証しろ、としている。どちらも難しい変更だ。

スペイン:N3663と同じ方法で、グローバルなoperator delete[]にもサイズを取れるオーバーロードを追加しろ。

スペイン:リテラル型のクロージャーオブジェクトを作れるようにしろ。

スペイン、アメリカ合衆国:[[deprecated]]をCDに入れ忘れてるぞ

アメリカ合衆国:可変長配列の長さが0だと実行時例外だが、C99だと合法だしC++14でもコンパイル時には合法な既存のコードが、C++14だと実行時例外になるぞ。負数はともかく0は除外しろ。

[PDFとかワロエナイ] N3734: Vector Programming: A proposal for WG21

ベクトルプログラミングに関するプレゼン用のスライド。多分シカゴで使われるのだろう。

[PDFとかメシマズ状態] N3735: On the difference between parallel loops and vector loops

提案ではなく、パラレルループとベクトルループの違いの解説。

N3739: Improving Pair and Tuple (Revision 1)

pairとtupleに関する些細な不便を解消する提案。以下のようなコードが動かないのを修正する。

std::tuple<int, int> pixel_coordinates() 
{
  return {10, -15};  // Oops: Error
}

struct NonCopyable { NonCopyable(int); NonCopyable(const NonCopyable&) = delete; };

std::pair<NonCopyable, double> pmd{42, 3.14};  // Oops: Error

[PDF死すべし] N3740: A Proposal for the World's Dumbest Smart Pointer, v2

何もしないバカなスマートポインターを標準ライブラリに追加する提案。これは生のポインターと同等であるということを明示的に示すためだけのラッパークラス。

[PDFが打ち棄てられるのをこの目で見たい] N3741: Toward Opaque Typedefs for C++1Y, v2

これはC++14ではなく、C++1Yに向けた提案。

BOOST_STRONG_TYPEDEFの機能を、もっとまともにして、コア言語でネイティブサポートする提案。strong typedefという名称がすでに知られていているが、opaqueを使うという。どうも日本語には優しくない名称だ。オペイクというが不透明といおうが非通過といおうが、やはり日本語では通じない。また、型の別名を宣言するのは、typedefの印象が強いので、タイトルではopaque typedefといっているが、実際にはエイリアス宣言を拡張する。そのため、opaque aliasというのが正しい。

非通過エイリアスは、通常のエイリアス宣言による型の別名宣言とは違い、異なる型を宣言する。たとえば、オーバーロード解決でも異なる型として区別されるし、テンプレートのインスタンス化でも異なる型として区別される。

・・・と、冒頭を読んだ時点では思っていたのだが、opaque aliasはやたらと大掛かりな機能であることが分かった。2013-001にでたN3515の改訂版なのだが、当時じっくり読んでいなかったのはなぜだろうか。とにかく内容については別に書く。

[PDF stands for Psycho's Dumbass Format] N3742: Three <random>-related Proposals, v2

randomを簡易的に使うためのちょっとした小物ライブラリの追加。

[PDF SHALL NOT be supported at all] N3743: Conditionally-supported Special Math Functions for C++14, v2

C++11には脱落した数学用の関数の提案。規格準拠の実装は提供しなくてもかまわない。

[[impure_pdf]] N3744: Proposing [[pure]]

純粋関数(pure function)であることを明示するattribute、[[pure]]の提案。

pure functionとは、内部に状態を持たず(つまり関数内外の静的ストレージを変更せず)、外から副作用が確認できず、またI/O操作もなく、その出力である戻り値は、与えられた入力である実引数のみに影響され、また、同じ入力には必ず同じ出力を返す関数のことだ。純粋関数の他にも、健全関数(Well-behaved function)などと呼ばれていたりする。純粋や健全に反対する関数を、これまたそのまま、不純(impure)とか、不健全(ill-behaved)などと言ったりする。

そのような挙動が完全に実引数のみに依存し、同じ実引数には同じ戻り値を返すような純粋関数は、最適化のヒントとなる。例えば、int x = foo(3) + bar[foo(3)];のようなコードは、もしfooが純粋関数ならば、関数fooの呼び出しは一回のみに省略することができる。

関数の中身というのはコンパイラーには分からない。そのため、原則的に関数は不純なものとして扱わねばならず、プログラムの意味を変えてしまうような極端で危険な最適化を行うことはできない。もし、関数が純粋であることをソースコード中で明示的に示す文法があればいいのに。

既存のコンパイラーで、関数が純粋であることを明示できる文法を提供している実装例も、かなりある。

N3745: Feature-testing recommendations for C++

あるC++の実装がある機能をサポートしているか、またサポートしているとして、それはいつドラフト規格入りしたときの文面を元にした実装なのかということを示すための、機能テスト用のCプリプロセッサーマクロの名前を、規格化しようというもの。

私はCプリプロセッサーを使ういかなる機能にも反対の立場であるので、この提案にも当然反対である。

[PDFフォーマットはプレインテキストベースのフォーマットに置き換えるべき] N3746: Proposing a C++1Y Swap Operator, v2

スワップ演算子の提案。 a :=: b ; とすると、aとbの値を入れ替える演算子。もちろんオーバーロード可能

[PDFはユニバーサルなドキュメント用のフォーマットではない] N3747: A Universal Model for Asynchronous Operations

C++で非同期処理を本格的にサポートするために、std::futureを大幅に拡張するだとか、std::futureの拡張とキーワードによるresumable functionを追加するだとかいう提案が出ている。しかし、std::futureは非同期処理を実現するために拡張するライブラリとしては甚だ非効率的であり、間違った選択である。このような選択が行われると、将来の非同期プログラミングに支障をきたすであろう。非同期処理は純粋なコールバック関数形式が効率的だ。

とはいえ、std::future系の非同期処理を望むC++プログラマーの声ももだし難い。この論文では、両方のアプローチを統合して、ひとつのモデルとして扱い、またプログラマーがどちらの方法を使うか選べるようにする。この論文では、そのような統一モデルの実装を紹介する。

論文では全体的に、future方式は非効率的で、コールバック方式の方が効率的であるとしており、また高級なfuture機能は、より低級なコールバック機能により実装できる。そのため、高級機能であるfutureをC++の標準ライブラリのデフォルトの非同期処理とするのは、C++の標準ライブラリを、他の高級言語のライブラリと同じ地位に押し上げてしまう。そのため、C++を本当に必要としている現場では、そのような非効率的な標準ライブラリは無視され、環境依存の低級な機能が使われるだろうとしている。

[PDFは有害] N3748: Implicit Evaluation of "auto" Variables and Arguments

初期化子の式から型を決定してくれるauto指定子は便利だが、時に便利すぎることがある。

Matrix A, B ;
auto C = A * B ;

ここで、Cの型はMatrixとは限らない。operator *の実装次第だ。たとえば、MatrixクラスはExpression Template技法を使って、実際の行列計算を遅延させていたりするかもしれない。その場合、Cの型は式ツリーをExpression Templateでキャプチャーした、とてつもなく複雑なものになる。

ある場合は、このような挙動が好ましいが、ある場合は、評価済みの型が欲しいこともある。そこで、auto指定子の機能を拡張して、autoで受けた場合の型をプログラマーが指定できるようにしようではないかという提案。

クラスは、autoで受けた場合の型を指定できる機能を得る。論文では、このための文法として、operator autoを提案している。

class product_expr
{
public:
    product expr(const matrix& arg1, const matrix& arg2)
        : arg1(arg1), arg2(arg2) {}
    matrix operator auto()
    {
        matrix R(num rows(arg1), num cols(arg2));
        R= ∗this;
        return R;
    }
private:
        const matrix &arg1, &arg2;
} ;

これにより、autoで受けた場合の型とその型に変換するためのコードを、クラス側で記述できるようになる。

まだ文法については議論が多く、operator autoではなく、operatorのかわりにoperator autoをつかう変換関数にするべきだとか、

operator auto Matrix() ;

あるいはusing宣言の文法を拡張するべきだという意見も出ている。

using auto = matrix ;

また、autoを使う側で、たとえクラスがoperator autoを定義していたとしても、そのような挙動が望ましくない場合は、explicit autoを使うことで、この機能を無効化できる。

explicit auto C = A * B ;

他にも、この機能をサポートするためのtraitsをいくつかいれたりしている。

まあ、面白い機能だとは思う。

N3749: Constexpr Library Additions: functional

functionalのlessとかplusなどのクラステンプレートをconstexpr対応にする提案。

N3750: C++ Ostream Buffers

stream出力は競合しないことは保証されているが、出力がどうなるかについては保証されていない。そのため、複数のスレッドからstreamを使う場合は、何らかの同期処理が必要である。問題は、streamを同期する方法が統一されていないと、独立して書かれた複数のコードを混在させるのが難しくなる。そのため、streamを同期させる方法が標準で提供されているべきである。

過去に、N3535 Stream用のMutexとかN3678 Stream用のバッチ処理だとか、あるいはN3665 BUFSIZ以下のサイズの出力は分割されたりしない保証だのといった提案がなされてきた。

2013年7月にConcurrency Study Groupによってなされた会議では、明示的なバッファであるべきだという合意に達した。この論文は、そのような明示的なバッファ、ostream_bufferを提案している。

[PDF潰れろ] N3751: Object Lifetime, Low-level Programming, and memcpy

そうか、ついに来たか。

女「キャットフードを切らしてしまったわ」
男「そうか、ついに来たか」

ヒント:何と言うべきかわからないときは、「そうか、ついに来たか」と言えばよい。このセリフは一瞬にして緊張した雰囲気を演出し、しかもどのような状況でも使うことができる。

xkcd: So It Has Come To This

memcpyを使ってオブジェクトの内部表現のバイト列を直接コピーするというコードに対して、これまで規格は何も言及して来なかった。つまりは、Undefined Behaviorということだ。

例えば、以下のコードを考える。

const uint32_t bits = 0x9A99993F;
float x;
std::memcpy(&x, &bits, sizeof x); // #1
float y = x * x; // #2

ここで、floatとはIEEE-754の32bit単精度浮動小数点数のデータ型であると仮定する。このコードにはふたつの問題がある。

#1では、異なるオブジェクト間でmemcpyをしている。これは、規格で言及されておらず、挙動は未定義である。

#2では、float型のオブジェクトxの読み込みがあるが、xは初期化されていない。規格に照らしあわせれば、この挙動は未定義である。

ただし、このようなコードは低級なシステムプログラミング用のC++コードとしては頻出するコードである。規格はこのようなコードの存在を公式に認め、その挙動を定義すべきである。

この論文では、異なるが同じサイズのtrivial copyable tableのオブジェクトのバイト列をmemcpyを使ってコピーすることを、「初期化」、あるいはより公式に言うと「オブジェクト構築」であることを、規格で定義することを提案している。

[PDFを扱う際は一切の望みを捨てよ] N3752: Index Based Ranges

Rangeで複雑なイテレーターアダプターを積み重ねていくと、イテレーターのオブジェクトがどんどん太っていく。これを防ぐために、イテレーターへのインデックスを用いたRangeアダプターを提案しているというものらしいが、Rangeはよくわからない。

[PDFをみると身構えるのは適切な防衛的読書である] N3753: Centralized Defensive-Programming Support for Narrow Contracts (Revision 1)

ライブラリ側で、入力や事前条件が適切かどうかを入念に確認する、防衛的プログラミングをサポートするための機構を標準で提供する提案。

といえば聞こえはいいのだが、実際のところは、細分化されたCプリプロセッサーマクロを大量に提供するものになっている。いわば高機能版assertといえるだろう。

私はCプリプロセッサーに依存するいかなる機能にも反対の立場であり、この論文にも当然のごとく反対する。

N3757: Support for user-defined exception information and diagnostic information in std::exception

例外が発生した時、その理由を例外オブジェクトに託すことがよくある。問題は、例外が発生した場所では、詳細な理由が記述できないことがよくある。詳細な理由は、例外が発生したところから上のコールスタックにしかないことがある。

void read_file( FILE * f )
{
  ....
  size_t nr=fread(buf,1,count,f);
  if( ferror(f) )
    throw file_read_error(???);
  ....
}

例えばこのコードだ。例外を投げたくなる問題が発生した時点では、FILEクラスへのポインターしか与えられていない。ここで、エラー内容の記述として、ファイル名を使いたいが、その情報はここでは得られない。

そのため、std::exceptionを拡張して、タグとデータを託せるようにする。

void process_file( char const * name )
{
  try
  {
    if( FILE * fp=fopen(name,"rt") )
    {
      std::shared_ptr<FILE> f(fp,fclose);
      ....
      read_file(fp); //throws types deriving from std::exception
      ....
    }
    else
      throw file_open_error();
  }
  catch( std::exception & e )
  {
    e.set<errinfo_file_name>(name);
    throw;
  }
}

提案では、std::exceptionに、setというメンバー関数テンプレートが追加され、テンプレート実引数をタグとして使い、そのタグのネストされた型名typeの値を格納できるようにする。その格納された値は、getというメンバー関数テンプレートで同じタグ型を指定すれば取得できる。

なるほど、確かに型を自由に指定したいというのはあるし、複数格納したいというのもあるし、そのためには型を指定するためにテンプレート実引数を使う必要があり、そのテンプレート実引数はタグとしても使えるという、一石二鳥なインターフェースだ。

また興味深いことに、この提案では、以下のようなメンバー関数も提案している。

std::string diagnostic_information() const;

このメンバー関数が返す文字列のフォーマットは規定されていないが、様々な「推奨」が規定されている。

例外オブジェクトthisの指し示す動的な型名、whatの返す文字列、例外を発生させたthrow式のある__FILE__と__LINE__、throw式が書かれた関数名、setで格納されたすべての要素に対しostreamオブジェクトを引数にoperator <<を呼び出して得られた文字列、などなどの情報を文字列に入れることが「推奨」されている。

Boostによる実験的な実装もある。

N3758: Standard exception information types for std::exception

N3737のsetで使う標準のタグ型の提案。汎用的なタグがいくつか提案されている。

N3759: SIMD Vector Types

intやfloatのような基本型のSIMD版を追加しようという提案。たとえば、int16_vとかfloat_vのような名前の型名だ。これは、コア言語ではなくライブラリへの提案であり、実装依存のtypedefとなる。このような型を追加することで、C++実装にヒントを与えることができる。

思うに、どうもあまり筋のいい提案ではなく、多分受け付けられないだろうと思う。

N3760: [[deprecated]] attribute

タイトル通り、[[deprecated]] attributeの追加。このattributeが使われて宣言されたエンティティを使用すると、実装はそのエンティティがdeprecated扱いであることを知らせる実装依存のメッセージを何らかの方法で出力する。たとえば、コンパイル時の警告メッセージなどだ。

すでに同等機能を独自拡張として提供しているC++コンパイラーもある、たとえばGCC/Clangの__attribute__((deprecated))、MSVCの__declspec(deprecated)、Embarcaderoは、そのまま[[deprecated]]として提供している。

文法は各々異なるが、その機能については一貫していて、実装例は豊富だと言える。

提案では、単にdeprecatedであることを示すだけでなく、実装依存の方法で出力されるメッセージに含まれる文字列を指定することもできる。

N3761: Proposing type_at<> and value_at<>

コンパイル時にparameter packを扱うことに特化したライブラリの提案。std::tupleは、この目的のために使うこともできるのだが、実行時に使うことも想定しているライブラリのため使いづらい。そのため、コンパイル時の型リスト操作に特化したライブラリが望ましい。

型リスト(Template parameter packやFunction parameter pack)に対して、何番目の要素を指定して型や値を得ることができる。

N3762: string_view: a non-owning reference to a string, revision 5

文字列に対するviewを提供するクラス、string_viewの提案。文字列を表現する方法は様々ある。例えばstd::stringや、charへポインターとサイズなどだ。string_viewは、そのような様々な文字列表現に、共通のviewを提供するライブラリである。

N3763: Traversable arguments for container constructors and methods, wording revision 4

始点と終点を指し示す対のイテレーターによる範囲を表現するライブラリ、Traversableの提案。このライブラリは、従来、Rangeという名称でBoostを初めとしたC++界隈で知られていたが、Rangeという名称は曖昧であるので、会議による投票の結果、Traversableという名称になることに合意された。

日本語圏としては、Rangeはそれほど曖昧に感じないし、また電子レンジのような既存の言葉もあるため、語感もよく親しみやすいのだが、残念なことだ。トラバーサブルというのではいかにも馴染みがない。

N3764: Ruminations on relational operators

std::optionalライブラリを追加するにあたって、既存の比較演算子の具体的な意味や既存の標準ライブラリにおける利用方法について色々と問題や不整合が多い。std::optionalではどの比較演算子に対応し、またどのように使うべきかということを主張している論文。

[PDFはoptionalではなく絶対的に排除すべきだ] N3765: On Optional

std::optionalについての様々な細かい問題点についての提案。std::lessにoptional用の特殊化を付け加えようというのが一番大きな論点か。

N3766: The identity type transformation

C++11のドラフトに入りながら、惜しくも削られたメタ関数、identityの提案。

identityには、実は相当に古い歴史がある。

まず、SGI STLが、実引数をそのまま戻り値として返すoperator ()を持ったクラステンプレートとして、identityを提供した。その実装は以下のようなものだ。

template<class T>
class identity : public unary_function<T, T> {
  T& operator()(T& arg) const { return arg; }  // libstdc++ only?
  const T& operator()(const T& arg) const { return arg; }
};

つまりはidentity functorというわけだ。libstdc++も、同等機能のidentityを提供している。

もちろん、このidentityはstd名前空間スコープに存在する。

そんな規格外の実装の都合など知ったことか、ということはできない。何しろ、SGI STLといえば、他ならぬSTLの父、Alexander Stepanov本人の本来の設計に基づいたSTLの実装なのだ(STLとは、STepanov Libraryの略である)

ところで、後にC++では、テンプレートメタプログラミングが興隆し、与えられた型テンプレート実引数をネストされた型名でそのまま返すidentityメタ関数の必要性がでてきた。以下のような実装だ。

template<class T>
class identity {
  typedef T type;
};

実装は簡単。問題は、名前だ。SGI STLのidentityと名前がかぶる。この歴史的な事情には、名前空間は役に立たない。SGI STLはもはや開発されていないが、libstdc++にもidentityは受け継がれているし、その影響力を考えれば、規格と言えども、identityという名前をstd名前空間スコープに気軽に持ち込むことはできない。

C++11のドラフトでは、Library Issue 700の提案を受けて、両者をマージさせることでidentityを規格に持ち込もうとした。つまりはこういうことだ。

template<class T>
class identity : public unary_function<T, T> {
    T& operator()(T& arg) const { return arg; }  // libstdc++ only?
    const T& operator()(const T& arg) const { return arg; }
    typedef T type ;
};

ところが、この折衷案に対しては、identity<void>がうごかないぞといった823939といった問題が持ち上がり、さらにはstd::forwardの実装がidentityを利用しなくなったことにより、公式なC++11規格では、削除されてしまった。

しかし、やはりidentityメタ関数は必要である。ではどうするのか。decayやremove_referenceを使うという手もある。

もうひとつの手は、名前を変えることだ。論文では、itentity_ofという名前が提唱されている。

9 comments:

Anonymous said...

いつもありがとうございます。勉強になります。

ところでN3750へのリンク・タイトルがなく、N3749の説明とN3750の説明が連続してしまっているように見受けられます。幸い、N3750はHTMLなので、[PDFウンコ]シリーズのネタをもう1つ考える必要はないでしょう。

他にも>pre<タグを付ける位置がちぐはぐなのが何箇所か……お疲れのようですね。

Anonymous said...

&lt;と&gt;を間違えたorz

Anonymous said...

> あまり文字列処理に優秀ではないプログラマーや、簡易的なプログラミング言語やツールでもパースが容易であり、人間にも機械にも処理しやすい。

SGMLやHTML5のパースが容易だとか口が裂けても言えそうにない。お前は一度でもパーサーを書いてみたことがあるのかと。類例:「C++なんて簡単だ」

> 低確率だがユーザーの秘密のパスワードを引き当てることができるとしたら、そのような規格は信用できない。

未定義動作を引き起こした任意のマルウェアが実行されてもプログラマーの自己責任というのが当たり前の言語で今さら何言ってるんだとしか。

江添亮 said...

一つ目、それを言い出すと、自然言語のパースはHTMLより難しい。
私が言っているのは、プレインテキストとして、grepやsedのような簡単なツールで簡単な処理ができることです。

二つ目、そのような規格というのはJavaのことで、C++のことではありません。原論文が書いているように、C++では、信頼できるコードと信頼できないコードを混在してコンパイルして、ひとつのプログラムとして実行するような利用方法は想定していません。

まあ、ChromiumがNative Clientで、C++から生成もできるネイティブコードを安全に実行する方法を模索していたりするので、将来的にはわかりませんが。

Anonymous said...

> 複数のオブジェクト艦でのアトミック性を保証したければ、複数の操作を排他的なロックでガードしなければならない。

「オブジェクト間での」ですよね。というのは置いといて、
memory_order_seq_cstならば異なるatomic変数間でも操作の順序が保証されます。
規格書の29.3.3にある "a single total order S on all memory_order_seq_cst operations" ですね。

江添亮 said...

それはひとつのアトミックオブジェクトに対する操作です。[1.10 p6]

Anonymous said...

> それはひとつのアトミックオブジェクトに対する操作です。[1.10 p6]

いいえ、違います。
1.10 p6 で述べられている "modification
order" と、 29.3 p3 で述べられている "a single total order S" は別のものです。

江添亮 said...

おや本当だ。
"for all affected locations"だとは。

Anonymous said...

N3710 の2つめの例で「x == y == 42」と書いていますが、
原文がそもそも間違っていて「r1 == r2 == 42」が正しいはず。
Proposed wording を見ても、この例で r1,r2 の最終的な値を
問題にしていることは明らかです。