web-dev-qa-db-ja.com

不完全な型はクラスでは許可されていませんが、クラステンプレートでは許可されています

以下は無効なコードです。

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つの質問があります。

  1. これは確かに有効なC++コードですか、それともコンパイラの癖ですか?
  2. それが有効なコードである場合、この例外を扱うC++標準の段落はありますか?
  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; } 

もう一度 コンパイルに失敗します

20
Goran Flegar

本当の答えは¯\ _(ツ)_ /¯かもしれませんが、テンプレートは不思議なので、おそらく現在は大丈夫ですが、他のいくつかのコアの問題の解決を保留している方が明確ではないかもしれません。

最初に、もちろん主な問題は [[class.mem]/14 です。

非静的データメンバーは、不完全な型を持たないものとします。

これが、テンプレート以外の例の形式が正しくない理由です。ただし、 [temp.point]/4 によれば、

クラステンプレート特殊化、クラスメンバーテンプレート特殊化、またはクラステンプレートのクラスメンバーの特殊化(特殊化が別のテンプレート特殊化内から参照されるために特殊化が暗黙的にインスタンス化される場合)、特殊化が参照されるコンテキストが依存する場合テンプレートパラメータで、特殊化が囲んでいるテンプレートのインスタンス化の前にインスタンス化されていない場合、インスタンス化のポイントは、囲んでいるテンプレートのインスタンス化のポイントの直前です。それ以外の場合、そのような特殊化のインスタンス化のポイントは、その特殊化を参照する名前空間スコープ宣言または定義の直前になります。

これはfoo_impl<void>::barはインスタンス化されますbeforefoo_impl<void>、つまりbar型の非静的データメンバーがインスタンス化された時点で完了です。だから多分それは大丈夫です。

ただし、コア言語の問題 1626 および 2335 not-exactly-the-same-but-still-quite-完全性とテンプレートに関する同様の問題があり、どちらもテンプレートのケースを非テンプレートのケースとより一致させることを望んでいます。

全体として見ると、これは何を意味するのでしょうか?よく分かりません。

6
Barry

この例は明示的に許可されていると思います

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;
}
5
VTT

受け入れられた回答の詳細

受け入れられた答えが正しい説明であるかどうかはわかりませんが、今のところ最も妥当な説明です。その答えから外挿すると、ここに私の元の質問に対する答えがあります:

  1. これは確かに有効なC++コードですか、それともコンパイラの癖ですか? [有効なコードです。]
  2. それが有効なコードである場合、この例外を扱うC++標準の段落はありますか? [[temp.point]/4]
  3. それが有効なコードである場合、最初のバージョン(templateなし)が無効と見なされるのはなぜですか?コンパイラが2番目のオプションを理解できる場合、最初のオプションを理解できない理由はわかりません。 [C++は奇妙なため-クラステンプレートはクラスとは異なる方法で処理されます(おそらくこれを推測した可能性があります)。 ]

さらにいくつかの説明

何が発生しているように見える

mainfoo{}をインスタンス化すると、コンパイラは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のルールは適用されません。

0
Goran Flegar