Committee Draft 1への日本からのコメントのその後
C++0xのCommittee Draft 1への日本からのコメントのその後について適当に書いてみました。
C++ CD1 Comment Status
まあ原文を読んでもらえば済む話なのですが、半年経った今改めて振り返るためにまとめました。
結構数があるので今回はtypoの修正等を省略させてもらい、独断と偏見によって技術系の提案のみに絞ります。
- JP-5 raw-stringの結合規則
- raw-string(エスケープ文字を無効化する)と普通の文字列リテラルの結合を明確にせよという提案。
- いまだにopenになっている。どうもまだ解決していないらしい。
- 790. Concatenation of raw and non-raw string literals
- JP-8 decltype(T)にスコープ解決演算子::が使えない
- decltype(T)::value_typeとできないので困るということについて。
- これもopenのまま。
- 743. Use of decltype in a nested-name-specifier
- JP-9 ラムダ式にムーブキャプチャ &&がほしい
- ラムダ式に変数を取り込むキャプチャの方法としてコピーではなくムーブを使いたいという要望。
- 名前の付いた変数からムーブすることはできないとしてrejected。
- まともな解決策はあるのだろうか。
- JP-10 ラムダ式の匿名関数オブジェクトの型にresult_typeがほしい
- std::result_ofは指定された型のresult_typeを返すので、ラムダ式の型にもつけるべきだという主張。
- 今のstd::result_ofはdecltypeで作られてるので関係ないとしてrejected。
- JP-12 constexprの再帰を認めるべき
- 上限を付けて定数式の再帰をさせてくれという要望。
- 割と以前から議論されていて、UKやDEからも同様のコメントあり。
- 現在review段階。
- Issues with Constexpr
- constexprの再帰を認めるべき - meryngii.neta
- JP-14 inline namespaceの名前の重複について
- inline namespaceとすると外側の名前空間でもアクセスできるようだが、外側の名前空間と名前が重複したらどうなるのかということについて。
- openのまま。
- using directive(using namespace 〜;)が自動的に挿入されたようになる(7.3.1.8)と書いてあるので、それで理由づけられるような気がする。
- 812. Duplicate names in inline namespaces
- JP-21 char16_t/char_32_tの対応が不十分
- iostreamにあるwostreamにはu16ostreamをなど、文字列を扱うライブラリにUnicode版のtypedefを追加せよという要望。
- 型が増加すると大変だから、面倒くさがらずに変換してくれということでrejected。
- 現実的な対応ということなのだろうか。
- JP-23 フリースタンディング環境で使用できるヘッダの追加
- OSが起動していないような簡易な環境でも使えるヘッダとして
を追加してくれという要望。 だけは許可するべきじゃないかというわけでreview状態。 - 他に
なども議論されているようだ。 - http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1003
- OSが起動していないような簡易な環境でも使えるヘッダとして
- JP-27 標準ライブラリ関数にnoreturn属性を追加
- exitなどはユーザ側に処理が返ってこないことが明確なのでnoreturn属性を付けてほしいという提案。
- 見過ごされているような雰囲気。
- extern "C"な関数に属性を付けられるのだろうか。
- JP-28 例外クラスのUnicode対応
- std::exceptionなどで例外メッセージにwchar_tやchar16/32_tを使いたいという要望。
- 呼ぶ側が国際化せよということでrejected。
- JP-21と同じ見方でよいのだろうか。
- JP-30,31 nested_exceptionは木構造をサポートすべき、または削除すべき
- 例外が連鎖するような場合のためにnested_exceptionというものが追加されたが、木構造をサポートしないと使い物にならないという話。
- openのまま。
- 関数の広がりは分岐しながら進んでいくので、自然に木構造になるということだろうか。
- id:wraith13氏ががんばってくれたらしい。C++0x の nested_exception について 其の参(考察) - TrickDiary
- JP-32 exception.what()のメッセージが役に立たない
- 例外のメッセージを見ても何の例外かも分からないようなものばかりなので、役に立つものにするよう明記してくれという要望。
- 実装依存、ということでrejected。
- JP-38 bindの戻り値の型がムーブできない
- bindの戻り値の関数オブジェクトは、ムーブが実装されておらずコピーされる可能性がある。
- open状態だが解決案は提示されている。
- USやDEからも同様のコメントあり。
- JP-62 is_xxx関数がイテレータを返すのに違和感がある
- 一般的にis_xxxはbool型を返すというルールがあるのに、is_sorted_until、is_heapというイテレータを返す関数がある。そこでこれらの名前はxxx_boundに変えるべきという提案。
- 名前について合意が得られていないとしてrejected。
- この名前になった元々の合意とやらはあるのだろうか…。
- JP-63 discrete_distributionにinitializer_list版のコンストラクタがほしい
- discrete_distributionという乱数のクラスでイテレータだけでなく初期化リストによっても初期化できるようにしてほしいという要望。
- これはaccepted。最新のhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2836.pdf:N2836を参照とのこと。
- 辞書によると離散型分布というものらしい。よく分からない。
- JP-64 valarrayのinitializer_list版のメンバ
- valarray
& operator+= (initializer_list ); が定義されていないことについて。 - 見過ごされている予感。
- ところでvalarrayって何に使うのだろう。
- valarray
- JP-74 basic_regexのinitializer_list版のメンバ
- JP-64との関連で、basic_regx & operator= (initializer_list
); が定義されていないことについて。 - tentatively ready(とりあえず準備完了)状態。
- JP-64との関連で、basic_regx & operator= (initializer_list
- JP-65,66 unspecified-bool-type からexplicit operator bool() constへ変更すべき
- せっかくExplicit Conversion Operatorsがあるのだから使おうぜという話。
- 見過ごされている予感。
- JP-73
のUnicode対応 - fstreamのファイル名はchar16/32_tどころかwchar_tも使えないということについて。
- まだ議論されていないようだが、JP-21,28と同じ方針でrejectされそうな予感。
- JP-75 Atomic Operation Libraryのenum/structの定義方法がCの古い文法になっている
- typedef struct { 〜 } 〜;などの古い記法が使われているので直すべきという提案。
- C言語との互換性を保つためにわざとこうしてあるとしてrejected。
- JP-76 "Throws: Nothing"の記述ポリシーが一貫していない
- Throws: Nothingが書かれていないものがあることについて。
- 編集的な問題ではないとしてopenのまま。
コンセプトに関する提案(JP-20,26,29,33,39〜43,45,46,48〜50,52,53,55,60,67〜69,72,77,79,81)は、コンセプトが追放された模様なので扱いませんでした。復活したら書くかもしれません。
型消去と引数転送
右辺値参照 + 可変個引数テンプレートでBoost.Functionの真似事をしようと思ったのだが、なんだか面倒な問題にぶち当たってしまったらしい。
#include <utility> template <typename... Args> struct my_function { template <typename T> struct object_invoker { static void invoke(void* ptr, Args&&... args) { (*static_cast<T*>(ptr)) (std::forward<Args>(args)...); } }; template <typename T> void operator = (T object) { this->ptr = new T(object); this->invoker = &object_invoker<T>::invoke; } template <typename... Args2> void invoke(Args2&&... args) { invoker(ptr, std::forward<Args2>(args)...); } void* ptr; void (*invoker)(void*, Args&&...); }; struct A { void operator() (int) { } }; int main() { my_function<int> f; f = A(); int i1 = 0; f.invoke(i1); const int i2 = 0; f.invoke(i2); // エラー }
様々な関数オブジェクトを使えるようにするために型消去*1と呼ばれているらしいテクニックを使っている。また、引数転送に右辺値参照*2を使っている。右辺値参照はあまりに絶妙なので試すたびに騙されたような気分になる。あと、もちろん可変個引数テンプレートは引数を複数個取るために使っている。
しかしこのコードは期待通りには動かない。よく考え直してみると、型消去のテクニックでinvokerはただの関数ポインタになるので、それ以降は関数テンプレートの引数推論が使えない。つまりそれ以降は引数転送ができないのだが、呼び出される関数はさらに先にあるので、不完全な転送になるのだと思われる。
object_invoker
組み合わせているテクニック同士の相性が悪いのだろうか。それとも私の新機能の認識が根本的に間違っているのだろうか。一体Boostではどうやって実装するのだろう。
conceptが…
少し遅れたけれども、
C++0x 6 (2ch)
The Digitalmars-d-announce July 2009 Archive by thread
Conceptのお通夜会場はこちらです - にっき(pseudo)
C++0x Support in GCC
Concepts [no longer part of C++0x]
なんという…。
C++1xになるよりはいいか…。
ちなみにGCC 4.5ではExplicit conversion operators(explicit operator T()とかいうやつ)が使えるようになるらしい。
VMware上のUbuntuとフォルダ共有
これまでGCCを使うためにCygwin・MinGW・andLinuxなど様々と試したのだが、GCCをmakeするたびに必ずエラーが起きて、さすがにもう懲り懲りになった。かといって普段の作業はWindowsでやっているから、このためだけにLinuxに乗り換えるのも難しい。そんなときVMware Player上でUbuntuを試してみたら、これがなかなか使いやすいので、しばらくこれで実験してみようと思う。
インストールするだけなら非常に簡単だったが、Windowsとファイルを共有するには少し手間がいる。すでにいろいろなサイトで紹介されているが、自分用にメモを残すことにした。
ちなみに私の環境は次の通り。
- VMware Player 2.5.2
- VMware Tools 7.7.5(VMware Server 2.0.1より)
- Windows Vista SP1 … ホストOS
- Ubuntu 8.04 日本語版 VMware用仮想マシン … ゲストOS
まずUbuntuの仮想マシンをダウンロードして解凍する。最初からインストールされた形で提供されているので、VMware Playerでそのまま使うことができる。
http://www.ubuntulinux.jp/products/JA-Localized/vmware
VMware Playerもダウンロードしてインストールする。なおダウンロードには無料の登録が必要のようだ。*1
http://www.vmware.com/jp/download/player/
解凍した仮想マシンの中のUbuntu.vmxをダブルクリックすれば仮想マシンが起動する。ファイルの共有をしないならこれで終わり。実に簡単だ。
Windowsとファイルを共有したい場合はもう少し手間がかかる。
まず「アプリケーション」→「アクセサリ」→「端末」で端末を開いて、諸々をアップグレードする。
sudo apt-get update sudo apt-get -u dist-upgrade
dist-upgradeはあまり実行しない方がよいらしいが、普通にupgradeするだけだとlinux-header-genericなど必要そうなものがアップグレードできないようだ。
ファイルを共有するにはVMware Toolsをゲスト側にインストールする必要がある。またVMware Toolsは単独では配布されていないので、VMware Serverをダウンロードして取り出す。なお、Ubuntuの仮想マシン版にそのまま付属しているopen-vm-toolsではファイルの共有ができない。
VMware Toolsを取り出すために、Linux用のTARファイルをダウンロードする。ダウンロードするには同じく無料の登録をする。
http://www.vmware.com/download/server/
TARファイル内のvmware-server-distrib/lib/isoimagesにあるlinux.isoを適当に解凍する。
次に仮想マシンを起動する。VMware Playerのメニューの「デバイス」→「CD/DVD(IDE)」→「ディスク イメージ ファイル(iso)に接続」をクリックして、先ほどのlinux.isoを指定する。するとデスクトップに「VMware Tools」が現れるのでこれを開く。この中のTARファイルをどこかにコピーする。~/srcなどを作ってそこに置けばいいかと思う。
さらにこれを解凍する。ファイルを右クリック→「ここに展開する」で展開する。
再び端末を開く。まずUbuntuにあらかじめ入っているopen-vm-toolsをアンインストールする。
sudo apt-get autoremove open-vm-tools
そしてVMware Toolsをインストールする。
sudo ~/src/vmware-tools-distrib/vmware-install.pl
インストールはEnterを押し続ければよい。インストールが終わったら仮想マシンを終了して、Ubuntu.vmxに次の内容を追加する。
sharedFolder.option = "alwaysEnabled" sharedFolder0.enabled = "TRUE" sharedFolder0.present = "TRUE" sharedFolder0.writeAccess = "TRUE" sharedFolder0.readAccess = "TRUE" sharedFolder0.hostPath = "(共有フォルダのホスト上でのパス)" sharedFolder0.guestName ="(共有フォルダのゲスト上での名前)" sharedFolder0.eXpiration = "never" sharedFolder.maxNum = "1"
あとは仮想マシンを起動してVMware Playerのメニューの「VMware Player」→「共有フォルダ」で「常に有効」を指定してOKを押すと、共有フォルダが有効になる。
ゲスト上では/mnt/hgfs/(guestName)にフォルダが現れる。これでようやく共有ができるようになった。
GCC 4.4.0がリリース
http://gcc.gnu.org/gcc-4.4/
GCC 4.4.0がリリースされました。C++0xの機能の一部を取り込んでいます。これらを試すにはコンパイラに-std=c++0xというオプションを渡します。
http://gcc.gnu.org/gcc-4.4/cxx0x_status.html
右辺値参照、可変個引数テンプレート、初期化リストとこれらだけでもしばらく楽しめそうですね。コンセプトは現在別のbranchで開発中のようです。
volatileなオブジェクトのコピー(再び)
以前volatileとコピー・代入 - meryngii.netaということでvolatileにまつわるCとC++の非互換性について書いたのだが、C++の仕様書の最後で丸々このことが触れられていることに気づいた。せっかくなので拙訳を用意した。
付録C.1.8 12項. 特別なメンバ関数
12.8 (クラスオブジェクトのコピー)
変更点: volatileなオブジェクトのコピー
暗黙に定義されたコピーコンストラクタと暗黙に定義されたコピー代入演算子はvolatileな左辺値を作ることができない。例えば、次の例はISO Cでは合法である:
struct X { int i; };
struct X x1, x2;
volatile struct X x3 = {0};
x1 = x3; // C++では不正
x2 = x3; // これもまたC++では不正
論拠: いくつかの対案が長い間討論された。引数をvolatile const X&に変えると、クラスオブジェクトについて高速なコードを生成するのが非常に難しくなるだろう。暗黙に定義された演算子ごとに2つの別々のシグネチャを用意することについての議論は、曖昧性を生んだり、ベースクラスとメンバによるこれらの演算子の形成について明らかにするルールを複雑にしたりすることについてうやむやな問題を引き起こした。
元の機能への影響: 正しい意味を持つ機能の削除。
変換の難しさ: 意味上の転送。もしコピーにvolatileの意味が要求されるなら、ユーザ定義のコンストラクタか代入が用意されなければならない。もしvolatileの意味が要求されないなら、明示的なconst_castが使える。
どのくらい広く使われるか: ほとんど使われない。
大して使われないのにデフォルトの演算子を増やして複雑にするわけにはいかなかったということのようだ。確かにこれ以上C++に暗黙の動作を増やしたらどうなるかを考えると納得がいく。
operatorでストリームの書式状態を保存する
C++のストリームの書式状態は、一度変更すると再び変更しない限りそのままになる。書式フラグを元に戻すのを忘れていれば、同じストリームを使った別の部分のコードで思わぬ表示が出るかもしれない。
書式状態を保存するためのライブラリとしてboost::ios_stateというものがあるが、いちいちスコープが必要で、それぞれのクラス名も覚えなければならないのであまり使いやすくはない。
そこで、operator<と>をオーバーロードして、一文ごとにストリームの書式状態を復元する簡単なライブラリを作ってみた。これを使うとこのように書ける。
cout < hex < 256 < endl; cout << 256 << endl;
すると次のように表示される。
100 256
普通にすべてシフト演算子で書けば、2つ目もhexが効いて16進数で表示されるはずだが、この例だと1つ目の文が終わると状態が復元されて10進数で表示される。
このライブラリのコードは次の通り。
#include <iostream> template <class charT, class traits> class saved_stream { public: explicit saved_stream(std::basic_ios<charT, traits>& stream) : stream_(stream), is_last_(false) , flags_(stream.flags()), fill_(stream.fill()), precision_(stream.precision()), width_(stream.width()) { } saved_stream(const saved_stream& saved) : stream_(saved.stream_), is_last_(true) , flags_(saved.flags_), fill_(saved.fill_), precision_(saved.precision_), width_(saved.width_) { saved.is_last_ = false; } ~saved_stream() { if (is_last_) { stream_.flags(flags_); stream_.precision(precision_); stream_.width(width_); stream_.fill(fill_); } } private: std::basic_ios<charT, traits>& stream_; const std::ios_base::fmtflags flags_; const std::streamsize precision_, width_; const charT fill_; mutable bool is_last_; }; template <class charT, class traits> class saved_ostream : public saved_stream<charT, traits> { public: explicit saved_ostream(std::basic_ostream<charT, traits>& stream) : saved_stream<charT, traits>(stream), stream_(stream) { } private: std::basic_ostream<charT, traits>& stream_; template <class T> friend saved_ostream operator < (const saved_ostream& saved, T& value) { saved.stream_ << value; return saved_ostream(saved); } template <class T> friend saved_ostream operator < (const saved_ostream& saved, const T& value) { saved.stream_ << value; return saved_ostream(saved); } friend saved_ostream operator < (const saved_ostream& saved, std::basic_ostream<charT, traits>& (*value)(std::basic_ostream<charT, traits>&)) { saved.stream_ << value; return saved_ostream(saved); } friend saved_ostream operator < (const saved_ostream& saved, std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&)) { saved.stream_ << value; return saved_ostream(saved); } }; template <class charT, class traits, class T> saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream, T& value) { return saved_ostream<charT, traits>(stream) < value; } template <class charT, class traits, class T> saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream, const T& value) { return saved_ostream<charT, traits>(stream) < value; } template <class charT, class traits> saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream, std::basic_ostream<charT, traits>& (*value)(std::basic_ostream<charT, traits>&)) { return saved_ostream<charT, traits>(stream) < value; } template <class charT, class traits> saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream, std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&)) { return saved_ostream<charT, traits>(stream) < value; } template <class charT, class traits> class saved_istream : public saved_stream<charT, traits> { public: explicit saved_istream(std::basic_istream<charT, traits>& stream) : saved_stream<charT, traits>(stream), stream_(stream) { } private: std::basic_istream<charT, traits>& stream_; template <class T> friend saved_istream operator > (const saved_istream& saved, T& value) { saved.stream_ >> value; return saved_istream(saved); } template <class T> friend saved_istream operator > (const saved_istream& saved, const T& value) { saved.stream_ >> value; return saved_istream(saved); } friend saved_istream operator > (const saved_istream& saved, std::basic_istream<charT, traits>& (*value)(std::basic_istream<charT, traits>&)) { saved.stream_ >> value; return saved_istream(saved); } friend saved_istream operator > (const saved_istream& saved, std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&)) { saved.stream_ >> value; return saved_istream(saved); } }; template <class charT, class traits, class T> saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream, T& value) { return saved_istream<charT, traits>(stream) > value; } template <class charT, class traits, class T> saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream, const T& value) { return saved_istream<charT, traits>(stream) > value; } template <class charT, class traits> saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream, std::basic_istream<charT, traits>& (*value)(std::basic_istream<charT, traits>&)) { return saved_istream<charT, traits>(stream) > value; } template <class charT, class traits> saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream, std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&)) { return saved_istream<charT, traits>(stream) > value; }
このコードは一時オブジェクトを大量に生成するが、最適化が効けば通常は省略されるので特に問題とはならない。
もちろんフラグ以外の状態も復元される。自作のマニピュレータも自由に使える。
#include <iomanip> using namespace std; /* ... */ cout < setprecision(2) < hex < setw(15) < scientific < setfill('0') < 1234.5678 < endl; cout << 1234.5678 < endl;
0000001.23e+003 1234.57
私としてはなかなか使いやすいと思うのだが、演算子が紛らわしいかもしれない。operator <とoperator >の部分を置き換えれば簡単に改造することはできる。ぜひお試しあれ。