次のクラステンプレートがあるとします。
template<typename T>
struct Outer
{
struct Inner;
auto f(Inner) -> void;
};
Inner
の特殊化ごとにOuter
を個別に定義します。
template<>
struct Outer<int>::Inner {};
template<>
struct Outer<double>::Inner {};
次に、f
のすべての特殊化に対してメンバー関数Outer
を1回定義します。
auto Outer<T>::f(Inner) -> void
{
}
しかしClang(9.0.0)は不平を言います:
error: variable has incomplete type 'Outer::Inner'
auto Outer<T>::f(Inner) -> void
^
Inner
の他のすべての特殊化に対してOuter
の定義も提供することにより、コンパイラエラーを回避できます。
template<typename T>
struct Outer<T>::Inner {};
または、専門化ごとに個別にf
を定義します。
template<>
auto Outer<int>::f(Inner) -> void
{
}
template<>
auto Outer<double>::f(Inner) -> void
{
}
GCCとMSVCはどちらも最初のコードを受け入れます。これはClangのバグですか、それとも3つのうちの唯一の適合実装ですか?
Clangがコードを拒否するのは間違っていると思います。関数の宣言と定義は、
auto f(typename T::Inner) -> void;
// ...
template<typename T>
auto Outer<T>::f(typename T::Inner) -> void
{ }
この例では、T::Inner
は明らかに依存型です。したがって、Clangはインスタンス化するまでそれが不完全であると想定しない場合があります。あなたの例でも同じことが言えますか?私はそう言うでしょう。標準でこれを持っているので:
[temp.dep.type]
5 名前は、現在のインスタンス化のメンバーです
- ルックアップ時に、現在のインスタンス化であるクラスの少なくとも1つのメンバーまたはその非依存基本クラスを指す非修飾名。 [注:これは、クラステンプレートの定義で囲まれたスコープで名前を検索する場合にのみ発生します。 —メモを終了]
- ...
名前は、現在のインスタンス化の依存メンバーであり、現在のインスタンス化のメンバーである場合、検索すると、現在のクラスの少なくとも1つのメンバーを参照します。インスタンス化。
9 ある場合、タイプは依存しています
- ...
- 不明な専門分野のメンバー、
- 現在のインスタンス化の依存メンバーであるネストされたクラスまたは列挙、
- ...
したがって、パラグラフ9の最初の箇条書きはケースtypename T::Inner
。それは依存型です。
その間、あなたの事件は第二弾でカバーされます。 Outer::Inner
はOuter
の現在のインスタンス化に含まれる名前であり、さらにOuter
自体の中にあり、基本クラスには含まれていません。これにより、現在のインスタンス化の依存メンバーになります。この名前は、ネストされたクラスを指します。つまり、2番目の箇条書きのすべての条件が適用され、Outer::Inner
依存型も同様です!
どちらの場合にも、依存型があるので、コンパイラーはそれらを依存型として等しく扱う必要があります。私の結論は、GCCとMSVCは正しいということです。