以下は無効なコードです。
struct foo {
struct bar;
bar x; // error: field x has incomplete type
struct bar{ int value{42}; };
};
int main() { return foo{}.x.value; }
これは、foo::bar
は、foo::x
が定義されています。
ただし、同じクラス定義を有効にする「回避策」があるようです。
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
int main() { return foo{}.x.value; }
これは 機能する であり、すべての主要なコンパイラで使用できます。これについて3つの質問があります。
template
なし)が無効と見なされるのはなぜですか?コンパイラが2番目のオプションを理解できる場合、最初のオプションを理解できない理由はわかりません。void
の明示的な特殊化を追加した場合:
template <typename = void>
struct foo_impl {};
template<>
struct foo_impl<void> {
struct bar;
bar x; // error: field has incomplete type
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
int main() { return foo{}.x.value; }
もう一度 コンパイルに失敗します 。
本当の答えは¯\ _(ツ)_ /¯かもしれませんが、テンプレートは不思議なので、おそらく現在は大丈夫ですが、他のいくつかのコアの問題の解決を保留している方が明確ではないかもしれません。
最初に、もちろん主な問題は [[class.mem]/14 です。
非静的データメンバーは、不完全な型を持たないものとします。
これが、テンプレート以外の例の形式が正しくない理由です。ただし、 [temp.point]/4 によれば、
クラステンプレート特殊化、クラスメンバーテンプレート特殊化、またはクラステンプレートのクラスメンバーの特殊化(特殊化が別のテンプレート特殊化内から参照されるために特殊化が暗黙的にインスタンス化される場合)、特殊化が参照されるコンテキストが依存する場合テンプレートパラメータで、特殊化が囲んでいるテンプレートのインスタンス化の前にインスタンス化されていない場合、インスタンス化のポイントは、囲んでいるテンプレートのインスタンス化のポイントの直前です。それ以外の場合、そのような特殊化のインスタンス化のポイントは、その特殊化を参照する名前空間スコープ宣言または定義の直前になります。
これはfoo_impl<void>::bar
はインスタンス化されますbeforefoo_impl<void>
、つまりbar
型の非静的データメンバーがインスタンス化された時点で完了です。だから多分それは大丈夫です。
ただし、コア言語の問題 1626 および 2335 not-exactly-the-same-but-still-quite-完全性とテンプレートに関する同様の問題があり、どちらもテンプレートのケースを非テンプレートのケースとより一致させることを望んでいます。
全体として見ると、これは何を意味するのでしょうか?よく分かりません。
この例は明示的に許可されていると思います
17.6.1.2クラステンプレートのメンバークラス[temp.mem.class]
1クラステンプレートのメンバークラスは、それが宣言されているクラステンプレート定義の外部で定義できます。 [注:メンバークラスは、インスタンス化を必要とする最初の使用の前に定義する必要があります(17.8.1)。次に例を示します。
template<class T> struct A { class B; }; A<int>::B* b1; // OK: requires A to be defined but not A::B template<class T> class A<T>::B { }; A<int>::B b2; // OK: requires A::B to be defined
—エンドノート]
これは work もうまくいくはずです:
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
};
template<typename T>
struct foo_impl<T>::bar{ int value{42}; };
using foo = foo_impl<>;
int main()
{
return foo{}.x.value;
}
受け入れられた答えが正しい説明であるかどうかはわかりませんが、今のところ最も妥当な説明です。その答えから外挿すると、ここに私の元の質問に対する答えがあります:
template
なし)が無効と見なされるのはなぜですか?コンパイラが2番目のオプションを理解できる場合、最初のオプションを理解できない理由はわかりません。 [C++は奇妙なため-クラステンプレートはクラスとは異なる方法で処理されます(おそらくこれを推測した可能性があります)。 ]main
でfoo{}
をインスタンス化すると、コンパイラはfoo_impl<void>
の(暗黙の)特殊化をインスタンス化します。この特殊化は、4行目のfoo_impl<void>::bar
を参照しています(bar x;
)。コンテキストはテンプレート定義内にあるため、テンプレートパラメーターに依存し、特殊化foo_impl<void>::bar
は明らかに以前にインスタンス化されていないため、[temp.point]/4のすべての前提条件が満たされ、コンパイラは次の中間(疑似)コードを生成します。
template <typename = void>
struct foo_impl {
struct bar;
bar x; // no problems here
struct bar{ int value{42}; };
};
using foo = foo_impl<>;
// implicit specialization of foo_impl<void>::bar, [temp.point]/4
$ struct foo_impl<void>::bar {
$ int value{42};
$ };
// implicit specialization of foo_impl<void>
$ struct foo_impl<void> {
$ struct bar;
$ bar x; // bar is not incomplete here
$ };
int main() { return foo{}.x.value; }
[temp.spec]/4 に従って:
特殊化は、インスタンス化されるか、明示的に特殊化されるクラス、関数、またはクラスメンバーです。
したがって、テンプレートを使用した元の実装でのfoo{}.x.value
の呼び出しは、特殊化と見なされます(これは私にとって新しいものでした)。
明示的に特化したバージョンは、次のようにコンパイルされません。
特殊化が参照されるコンテキストがテンプレートパラメータに依存する場合
保持されなくなったため、[temp.point]/4のルールは適用されません。