パスワードを忘れた? アカウント作成
506693 journal

Yak!の日記: Two phase lookup, ADL そして using-declaration/directive

日記 by Yak!

Expression Template による文字列の連結」は operator overloading を使って実装されている。operator overloading は記述が簡潔になる一方、影響も大きいのでその利用の有無をライブラリ利用者側でコントロールできないものかと考えてみた。もちろん、マクロ定義によってヘッダで operator overloading をするかしないかを切り替えるとか、operator overloading 用のヘッダファイルを別に作るとかが簡単な案としてあるのだが、ヘッダ周りは共通にしたかった。operator overloading の定義自体はあるけど通常使われるかどうかを後からコントロールするという感じである。結局、結果は芳しくなかったのだが、その途上で色々と勉強になったのでまとめてみよう。

C++ Coding Standards の項目 57 番「型とその非メンバー関数のインタフェースは同じ名前空間に置くこと」これは Exceptional C++ #31~#34 (日本語版だと第5章) で書かれている「インタフェース原則」の帰結でもある。Web 上では

辺りが該当する記述である。つまるところこれは、Argument Dependent Lookup(ADL)に(非メンバ関数が)正しく引っかかるようにしようという事である。ADL 自体は簡単に言ってしまえば「関数の名前を探す際に、実引数の型の関連する namespace も検索する」ということになる。

さて、ADL において、using-declaration と using-directive について特別な扱いが存在する。(以下、参照規格は 14882:2003)

3.2.2/2 For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. (中略) Typedef names and using-declarations used to specify the types do not contribute to this set.
3.4.2/3 Any <I>using-directives</I> in the associated namespace are ignored.

つまり、using-declaration が存在する namepsace は associated namespace にはならず、また、associated namespace 内で using-directive で指定された namespace も考慮されない。じゃ、両方とも関係ないんだ、と思うのは早計で、using-declaration は associated namespace の決定には関与しないが、associated namespace 内で lookup を行う際には lookup の対象になるのである。

namespace ns3 { class Class3; }
namespace ns1 { class Class1; void func(ns3::Class3); }
namespace ns2 { using namespace ns1; class Class2; }
namespace ns3 { using ns2::Class2; using ns1::func; }

上記の例では、ns3 は ns2::Class2 の associated namespace とはならないし(associated namespace の決定では using-declaration は考慮されない)、ns2::Class2 に対して ADL を行っている場合には、ns2 は検索されるが、ns1 までは検索されない(associated namespace 内での using-directive は無効)。一方、ns3::Class3 に対する ADL では ns1::func() が見つかる(associated namespace 内での using-declaration は有効)ということになる。

ここで template が絡むと、もう一段階話がややこしくなる。template において dependent name (テンプレート引数に依存する名前 cf.14.6.2)の解決がインスタンス化時点まで遅延される、というのは template 使いにはよく知られていて two phase lookup あるいは two stage lookup と呼ばれている。規格でこれに言及しているのは以下の項である。

14.6.4/1 In resolving dependent names, names from the following sources are considered:
- Declarations that are visible at the point of definition of the template.
- Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.

気をつけなければならないのは instantiation context が使われるのは "declarations from namespaces associated with the types of the function arguments"、すなわち ADL の場合だという点である。ということで本来、以下のコードはコンパイルできない。

template<class T> void g(T t)
{
    f(t); //dependent
}
void f(const char*);
void h()
{
    g("hoge");
}

なぜなら、const char* は associated namespace を持たず、void f(const char*) が ADL では引っかからないし、また g() の定義時点では f() は可視ではないからである。が、試してみたところ、Comeau C++ 4.3.0 以降 (online版) では確かに compile できないのだが、VC++ 2003、VC++ 2005、g++ 3.4.4 では compile できてしまう。compie できる処理系では ADL だけではなく通常の lookup 自体も遅延されているようだ。ちなみにこの件では規格自体が 14.6/9 の例で誤りを犯していたりもする(cf. Core Language Issue 197)。

結局、規格に則れば instantiation 時点まで遅延されるのは ADL のみであり、operator overloading が通常使われるかどうかコントロールするには、ADL に引っかかるか引っかからないかをコントロールしなければならない。また、using-declaration も using-directive も ADL との関与が薄いため、唯一使えるのは associated namespace 内の using-declaration のみである。ということで、operator overloading を別 name space の置いておき(ADL には引っかからない)、ADL に引っかけたければ associated namespace 内に using-declaration を使って operator overloading の宣言を導入する、という形になる。ユーザー側でライブラリの namespace 内に変更を加えることになるし、これなら operator overloading を別ヘッダに分ける方が、簡潔でわかりやすい方法だろう。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

「科学者は100%安全だと保証できないものは動かしてはならない」、科学者「えっ」、プログラマ「えっ」

読み込み中...