思うに、自分は今まで、ユーザー定義リテラルのネガティブな面しか考えていなかったように思う。そこで、ポジティブな面について、考察してみることにした。まず、ユーザー定義リテラルは、単なるシンタックスシュガーであって、パフォーマンス上の利点はない。では、いったいどのように使えば、文法上の利点を得られるのか。
class gram ; gram operator "" _g(unsigned long long int) ; gram operator "" _kg(unsigned long long int) ; gram operator "" _pond(unsigned long long int) ; gram operator "" _kan(unsigned long long int) ; void f() { // ユーザー定義リテラルを使うと、とてもすっきり。 auto weight = 643_g + 3_kg + 4451_pond + 320_kan ; // ユーザー定義リテラルがない場合の、実現可能な文法。なんだか読みづらい。 // auto weight = g(633) + kg(3) + pond(4451) + kan(320) ; }
オーケー、これはクールだ。このようなコードならば、ユーザー定義リテラルも使ってみたくなると言うものだ。
しかし、アンダースコアから始まらなければならないという制限はどうしたものか。どうやって解決すればいいのだろう。ダブルアンダースコア、あるいは、アンダースコアに大文字で始まる名前は予約されている。アンダースコアで始まる名前は、グローバル名前空間において予約されている。このままでは、グローバル名前空間にユーザー定義リテラルのオーバーロード関数を定義することは出来ない。従って、クラス内か、何らかの名前空間の中で定義しなければならないのだが、クラス内はともかくとして、具体的な名前空間の中でしか定義できないのは苦痛である。第一、どうやって使うのだ。
またもし、グローバル名前空間に定義できたとしても、名前の衝突をどうやって避ければいいのか。上記の例に挙げたような、SI単位などの有名な名前は、みんな使いたがるはずだ。それに、この文法の性質上、みんな数文字程度の短い名前を使うに決まっている。長ったらしい名前など使いたがるわけがない。従って、複数のライブラリで名前が衝突すると言うことが、実際に起こりうる。それに、みんなアンダースコアひとつという一番簡単な名前は、自分が使いたいはずだ。
これを延々と考えていたが、ついに以下のような結論に達した。規格は正しい。つまり、ユーザー定義リテラルは、グローバル名前空間に定義してはいけないのだ、という事だ。規格の定義より明らかに、何らかの名前空間の中に定義しなければならない。では、どうやって実際に使うのか。それは、using directiveを使うのだ。
namespace SI { class gram ; gram operator "" _g(unsigned long long int) ; } void f() { using namespace SI ; gram weight = 100_g ; }
うれしいことに、こうすれば、名前の衝突と、名前の予約の問題を、同時に回避できる。名前の衝突は、その関数内だけを考えればよく、名前の予約の問題も、その関数の中だけで、グローバル名前空間には影響を及ぼさないので、問題はない。
規格を読む限り、well-formedにするには、こうするしかないと思われる。このことは一切説明されてはいないのだけれど。
追記:
using namespace hogehogeとやるほうが、using directiveであり、それではない方は、using declarationであった。名称を逆に覚えていたらしい。
using namespace SI;
ReplyDeleteor
using SI::operator "" _g;
やっぱりこう書くしかないですよねー。
using directiveは、その記述の煩雑さから、実際のコードでは、まずこれには使われないと思いますけどね。
ReplyDeleteusing directive と using declaration が逆ですよ。
ReplyDeleteおっと。逆だった。
ReplyDeleteトリプルアンダースコアで始まれば良いんですね、分かります
ReplyDeleteトリプルアンダースコアも、ダブルアンダースコアで始まっているので、無理だと思うのですが。
ReplyDelete'_'から始めるとか?
ReplyDeleteやめて欲しいですけどねw