C++0x、コンセプト除外の決断(3ページ目)

話題としてはもう古くなってしまったけど、C++0xにおいて、コンセプトが切り離された件で、Bjarne博士がDr.Dobbsに掲載している記事の、技術的な内容を綴った3ページ目(とくに、C++1xやC++yなどについて触れている部分)についても勝手に和訳が出来上がってしまったので懲りずにメモ。個人的にはコンセプトに関する予習とか殆どしてこなかったのだけど、どんな内容があって何が議論されていたのかがちょっとわかったような気にはなれたかなあ。
元記事はこちら⇒(http://www.ddj.com/cpp/218600111?pgno=3

技術的な議論
コンセプトに関して、解決していない問題は、明示的コンセプトマップと暗黙的コンセプトマップの区別をどうするかということに集中した。(BS2009を参照してほしい)

  1. あるコンセプトの要求に適合する型は、そのコンセプトが要求されている状況において、自動的に許容されるべきなのか(例えば「+,-,*,/」の四則演算演算子を提供しているXという型が、通常の算術演算機能を要求するコンセプトCに、自動でマッチされるべきなのか)、あるいは適合が意図的であることを示す明示的な追加の宣言(XからCへのコンセプトマップ)を必要とすべきか?(私の結論は、殆ど全ての場合について、自動で適合させるべき。)
  2. 自動コンセプトにすべきか明示コンセプトにすべきかといったような選択の余地があった方がいいのか、また、コンセプトの記述者が選んだその選択を、全てのユーザーに強要できる手段を与えるべきなのか(私の結論は、全てのコンセプトは自動であるべき)
  3. X::begin()というメソッドを持つXという型は、begin(T)という関数を要求するコンセプト Cに適合するとするべきなのか、あるいは、ユーザーがTからCへのコンセプトマップを提供すべきなのか?例えば、std::vectorとstd::Rangeの関係のように。(私の結論は、適合とすべき)

フランクフルト以前の、現状維持案は、私の提案とは全く異なるものだった。明らかに私はここで(このように)論理的根拠の大部分や詳細は割愛したとしても、分かりやすい説明をする必要があったと思う。

技術的な議論の全てをここで再現することはできないが、私の結論としては、現状維持案のコンセプトマップは以下の二つのことに使われるものだ。

  • 各種修飾語を追加・対応させることによって型をコンセプトに対応させ、
  • 型がコンセプトに適合することを宣言する。

どうもある人たちにとっては、この後者のところが、とりわけ大事な本質であるかのように思えてしまい、それがために、たまに運悪く生じ得るある必要性が、非常に稀であるという事実から目をそらせてしまうことになったように思える。

二つのコンセプトが意味的に異なる場合に本当に必要なのは、「この型はそっちのコンセプトではなくこっちのコンセプトに適合します」という宣言などではなく、「この型は、こっちのコンセプトの意味性を持つのであって、そっちのコンセプトの意味性を持つものではない」ということを明示宣言してやることなのである。

例えば、STLのinput iterator と forward iteratorのを引き合いに出して言えば、forward iterator によって定義された配列を2回巡ることはするだろうが、input iterators によって定義された配列でそれをやるのは意味をなさないという点で、両者間には決定的な違いがある。入力ストリームに対して、マルチパスなアルゴリズムを適用するのはまずかろう。
現状維持案による解決策は、いわばどの型がforward iterator にマッチし、どの型がinput iterator にマッチするのかを、全てのユーザーが指示することを強要するものである。
これに対して私の提案した解決策は、二つのコンセプトと、それらが互いに共有しないある意味性(をもつ型)を使用したい状況においては、コンパイラがどっちのコンセプトがその型に相応しいのかを推論できない場合においてのみ、その型が満たしている方の意味性を明示的に指定するというものだ。例えば、この型はマルチパス処理をサポートします、という具合に。
一体、ひとたびコンセプトマップというものを手にしてしまったら、何でもかんでも型−コンセプト宣言が必要だと考えがちになってしまうということはあるのかも知れない。
フランクフルト会議で、私は次のように総じた。

  • なぜコンセプトが必要なのだろうか?
    • テンプレートの明示的な引数にされる型に対しての要求を定義するため。
      • 正確な説明
      • よりましなエラーメッセージ
      • オーバーローディング

異なる人々は異なる視点と優先基準をもっている。しかしながら、このように高レベルのところの議論においては、論争は全くないか殆どないにしても、混乱が生じる可能性がある。全ての不十分なコンセプトの設計が、そのような様相を呈した。(←訳:まったく自信なし)

  • どんな心配がされているか
    • プログラミングのしやすさ
    • 正式な仕様の複雑化
    • コンパイル時間
    • 実行時間

個人的な心配は、一つ目はプログラミングのしやすさ(簡単であること、一般性、教えやすさ、拡張性)にあり、そして言語仕様の複雑化(40ページにもわたる標準文書だ)が、二番目にくる。しかしながら、私は試験的な実装(ConceptGCC[Gregor2006])は、(コンセプトを使った)拘束条件つきのテンプレートの実行時間は、現状の拘束なしのテンプレートと同等か、それを上回るものだったという認識でいる。たしかにConceptGCCはひじょうに遅いものだったが、そのこと自体が根本問題だとは私は考えない。

発想を評価する段階にきて、我々は古くからあるジレンマに突きあたることになった。進退極まったジレンマの最たるものは、簡単に言えば、以下のようなだ。

  • 商用の実装を置き去りにして標準化をするべからず
  • 大多数の実装者は標準を抜きにしては実装しない

詳細な設計と実験的な実装というものはどうにも常に、妥協の基盤、たたき台とならなくてはならないのである。
私のコンセプトに対する考え方は

  • Duck typing()
    • GPとしてのテンプレートの成功の鍵(インタフェースやその他多くをもつOOと対比的な意味で)
  • Substitutability
    • 「保障されている」ということよりも強い前提条件を持つ関数を呼ぶ必要がないこと
  • Accidental match is a minor problem 事故的な意図しないマッチはたいした問題ではない
    • 問題のリストのトップ100には入らない

私がフランクフルト以前のワーキングペーパーに組み込んだコンセプトへの最小限の修正は、

  • コンセプトは暗黙的/自動
    • ダック・タイピングが「あり」であり続けられるために
  • 明示的な改良
    • Substitutabilityを扱うため
  • General scoping of concept maps コンセプトマップのジェネラルスコーピング
    • To minimize implementation leakage 実装の漏れを最小化するために
  • 簡易な型・コンセプトマッチング
    • 冗長なコンセプト抜きで、vectorが、rangeとして使えるようになるため

以上詳細は、BS2009を参照いただきたい。

コンセプトが切られてもなお、次のC++標準の発行は遅れることになりそうだ。悲しいことに、C++0xはなくなることになる(C++03への若干の修正を含まないものと考えれば)。我々はC++1xまで待たなくてはなるまい、そして、xにくるのが小さい数字であることを願いたいところだ。
しかし希望もある。何故なら、C++1xの目玉項目としては既に完成図が出来上がっているためだ。(もっとも、どこかの国家標準化機関(訳:ISOのこと?)が、標準への正式な提案という位置づけにある目玉項目に対して強弁をふるってこなければの話だが)残されていることの全ては、未解決の技術的な問題やコメントを解決している膨大な作業だ。

目玉項目のリストやそれに関するいくつかの議論は、私のC++0xFAQの中に見つけることができる。

  • atomic operations
  • auto (type deduction from initializer)
  • C99 features
  • enum class (scoped and strongly typed enums)
  • constant expressions (generalized and guaranteed; -constexpr)
  • defaulted and deleted functions (control of defaults)
  • delegating constructors
  • in-class member initializers
  • inherited constructors
  • initializer lists (uniform and general initialization)
  • lambdas
  • memory model
  • move semantics; see rvalue references
  • null pointer (nullptr)
  • range for statement
  • raw string literals
  • template alias
  • thread-local storage (thread_local)
  • unicode characters
  • Uniform initialization syntax and semantics
  • user-defined literals
  • variadic templates

and libraries:

  • Improvements to algorithms
  • containers
  • duration and time_point
  • function and bind
  • forward_list a singly-liked list
  • future and promise
  • garbage collection ABI
  • hash_tables; see unordered_map
  • metaprogramming and type traits
  • random number generators
  • regex a regular expression library
  • scoped allocators
  • smart pointers; see shared_ptr, weak_ptr, and -unique_ptr
  • threads
  • atomic operations
  • tuple

コンセプトが抜きになったとしても、C++1xはC++98に対する大きな改善となるだろう。そして、これらの目玉機能が、最大限の表現力と柔軟性を兼ね備えたものであることを発見して欲しい。私はコンセプトがこの5年以内にC++のリビジョンに含まれるようになることを願っている。もしかしたらそれをC++1yもしくは、C++yと呼ぶのもいいかも知れない!