環境
- C++11
- Visual Studio 2017 15.8.1
- gcc version 5.4.0
概要
「two-phase name lookup」に関する自分の理解は以下の通りです。
- 先にテンプレート引数に依存しないものを名前解決する。
- 次にテンプレート引数に依存するものを名前解決する。
しかし、なぜ2段階で名前解決する必要があるのでしょうか。
「two-phase name lookup」をしないで、全部解析してから名前解決を行うのではなにか問題があるのでしょうか。
質問の背景
以下のコードが Visual C++ ではコンパイルが通り、gcc では通らない原因について「C++ - gcc でコンパイルすると、親クラスのメンバー変数を子クラスから参照できない問題について」で質問させていただきました。
そうしたところ、「two-phase name lookup」という仕様によるものであるということをご教示いただきました。
cpp
1#include <iostream> 2 3// 親クラス 4template <class Child, class SomeType> 5struct Base 6{ 7 void interface() 8 { 9 static_cast<Child *>(this)->implementation(); 10 } 11 12 void set_value(SomeType value) 13 { 14 member_ = value; 15 } 16 17 public: 18 SomeType member_; 19}; 20 21// 子クラス 22template <class SomeType> 23struct Derived : Base<Derived<SomeType>, SomeType> 24{ 25 void implementation() 26 { 27 std::cout << member_ << std::endl; 28 } 29}; 30 31int main(int argc, char *argv[]) 32{ 33 Derived<int> obj; 34 obj.set_value(1); 35 obj.interface(); 36}
なぜ Visual C++ ではビルドが通ったのか調べたところ、Visual C++ 15.3 以前では「two-phase name lookup」が実装されておらず、15.3 以降でもデフォルトでは無効になっており、/permissive-
コンパイラオプションを有効にすると、機能するとのことでした。
C++ Team Blog | Two-phase name lookup support comes to MSVC
なので、試しに /permissive-
オプションを有効にしてビルドしたところ、gcc と同様、名前解決に失敗することが確認できました。
エラー: C3861: 'member_': 識別子が見つかりませんでした
ここで思ったのは、VC++ でデフォルトでは「two-phase name lookup」をしないで、名前解決できているのだから、なぜこの仕様が必要なのかというのが質問に至った背景です。
すみませんが、よろしくおねがいします。
回答いただいた内容に関する理解
alphya さんにご回答いただいた内容を自分の勉強用に整理しました。
もし誤った解釈があれば、ご指摘いただければ幸いです。
- テンプレートの元々の意図は、型が異なり内容が同じクラスや関数を複数作る必要がないようにするため。(例: sqrt, sqrtf, sqrtl)
- 昔はテンプレート引数に非依存の識別子を先に名前解決することは、許可及び推奨されていたが、必須ではなかった。
- そのため、識別子の名前解決はテンプレートの定義の解析時には行わないで、テンプレートを実体化するまで名前解決を遅らせることができた。
「two-phase name lookup」が必要になった理由
テンプレートの実体化時に名前解決を行う方法だと、テンプレートが前後の文脈に依存する状況がおき、バグを引き起こしやすくなる。
例:
cpp
1#include <cstdio> 2 3void func(void*) { std::puts("The call resolves to void*") ;} 4 5template<typename T> void g(T x) 6{ 7 func(0); 8} 9 10void func(int) { std::puts("The call resolves to int"); } 11 12int main() 13{ 14 g(3.14); 15}
- two-phase name lookup を行う場合
テンプレートが定義された段階で template<typename T> void g(T x)
の中身の解析が行われる。
この時点で func(0)
にマッチするものは、void func(void*)
だけなので、void func(void*)
に名前解決される。
- two-phase name lookup を行わない場合
g(3.14)
でテンプレートが実体化された時点で template<typename T> void g(T x)
の中身の解析が行われる。
この時点で func(0)
にマッチするものは、void func(int)
と void func(void*)
の2つがあるので、void func(int)
に名前解決される。
もし、void func(int)
がなかったならば、void func(void*)
に名前解決される。
このように、テンプレートの後に void func(int)
があるかどうかでテンプレートの意味が変わってしまう。 (前後の文脈に依存してしまう)
MSVC のバージョン 15.3 以前の実装
- テンプレートは宣言だけチェックして、定義の部分は記録だけしておき、実体化されるまで中身のチェックはしない。
- テンプレートの実体化が必要になった段階で、記録してあるリストからテンプレートの実装を探してきて、中身をチェックする。
例1: テンプレートの中身は、実体化されるまでチェックされない。
#include <cstdio> template <typename T> void hoge(T t) { 1 // ; がない } int main() { }
gcc: source_file.cpp:5:6: error: expected ';' after expression
vc++: コンパイルが通る。
例2: テンプレートが定義された段階では曖昧さがあっても、実体化された段階で曖昧さがないケース
cpp
1#include <cstdio> 2#include <vector> 3 4template<class C> 5void f(const C &container) { 6 C::const_iterator *x; 7} 8 9int main() 10{ 11 std::vector<int> v; 12 f(v); 13}
テンプレートが実体化されていない段階では、C::const_iterator
は型なのかクラス C
で定義された static 定数なのかわからない。
C::const_iterator
が型と解釈した場合:C::const_iterator *x;
は型C::const_iterator
のポインタ*x
C::const_iterator
が定数と解釈した場合:C::const_iterator *x;
は定数C::const_iterator
と変数x
の乗算
C::const_iterator
を型と認識させるには typename C::const_iterator
と指定する必要がある。
cpp
1#include <cstdio> 2#include <vector> 3 4template<class C> 5void f(const C &container) { 6 C::const_iterator *x; 7} 8 9int main() 10{ 11 std::vector<int> v; 12 f(v); 13}
gcc:
source_file.cpp: In function ‘void f(const C&)’: source_file.cpp:6:22: error: ‘x’ was not declared in this scope C::const_iterator *x; ^ source_file.cpp: In instantiation of ‘void f(const C&) [with C = std::vector<int>]’: source_file.cpp:12:8: required from here source_file.cpp:6:21: error: dependent-name ‘C:: const_iterator’ is parsed as a non-type, but instantiation yields a type C::const_iterator *x; ^ source_file.cpp:6:21: note: say ‘typename C:: const_iterator’ if a type is meant
vc++: コンパイルが通る。
two-phase name lookup を行う gcc は実体化前に解析するのでエラーとなるが、MSVC は実体化された段階で解析するので、この時点では C::const_iterator
は型と判明しているのでエラーにならない。
参考文献
回答2件
あなたの回答
tips
プレビュー