今まで文法的な違いしか理解していなかったので、詳しく学ぶことにした。
using directiveは、名前空間を、あるスコープの中に持ち込む。
namespace A { typedef int foo ; typedef int bar ; } // Using directiveはグローバルに書ける。 using namespace A ; // 関数の中に書ける。 void f() { using namespace A ; foo a ; bar b ; } // ローカルスコープの中に書ける。 void g() { { using namespace A ; //範囲、ここまで。 } } //名前が衝突すると曖昧、どちらの名前も対等に扱われるから。 typedef int foo ; void h() { using namespace A ; foo a ;// Error }
一方、using declarationはというと、名前を宣言する
namespace A { typedef int foo ; typedef int bar ; } // 宣言できる所ならどこでも。 using A::foo ; // クラスの中でも struct S { using A::foo ; } // 関数の中もよし void f() { using A::foo ; } // 名前が衝突した場合、優先される。 typedef char foo ; void g() { using A::foo ; foo a ; // int }
そんなわけで、using directiveをユーザー定義オペレーターに使うと問題があるかもしれない。
たとえば、ある処理系が、グローバル名前空間に、_a という独自のリテラルオペレーターを持っていたとする。これは、あり得る話であり、規格準拠でもある。(アンダースコアひとつで始まる名前は、グローバル名前空間において、処理系の実装のために予約されている。)
// 処理系によってあらかじめ用意されているユーザー定義リテラル、_a int operator "" _a(char *) ; namespace A { int operator "" _a (char *) ; } void f() { // このコード自体はwell-formedであるが、 using namespace A ; int x = "hello,world!"_a ; // Error! どちらの_aなのか曖昧。 }
using directiveで導入される名前は、対等に扱われるので、using directive自体を使うことは問題がないのだが、実際にその衝突している名前を使う際に、曖昧になってしまう。
一方、using declarationならば、問題がない。
// 処理系によってあらかじめ用意されているユーザー定義リテラル、_a int operator "" _a(char *) ; namespace A { int operator "" _a (char *) ; } void f() { using A::operator "" _a ; int x = "hello,world!"_a ; // OK. A::operator "" _aが優先される。 }
なるほど、そうなると、安全を期すためには、using declarationを使うしかない。ユーザーの負担を和らげ、また単純なミスを防ぐためには、ライブラリ側でプリプロセッサのマクロを提供するしかない。已んぬる哉。
果たして、ユーザー定義リテラルは必要なのだろうか。現在、メジャーなコンパイラはどれも、アンダースコアから始まる一連の予約語をユーザーが使用したとしても、エラーを出さない。なぜならば、已に書かれたコードをぶち壊してしまう可能性があるからだ。ユーザー定義リテラルには、互換性の問題はないから、ユーザー定義リテラルの場合だけエラーにする事もできるが、果たしてすべてのコンパイラが、それほど順法意識が高いだろうかというと、疑問である。
最悪なのは、まず初めにユーザー定義リテラルを正式にサポートしたC++0xコンパイラが、この辺の予約語やアンダースコアに関する問題を一切無視してコンパイルを通す実装にしてしまった結果、アンダースコアを用いないリテラルオペレーターのコードが、世に広まり、後の規格改定で、組み込みのリテラルを増やす際に、影響を及ぼすと言うことだ。
No comments:
Post a Comment