3

私の最初のコードフラグメントはコンパイルされ、正常に動作します:

template <class x> struct B1
{
    template <class x> struct B2     { int getX() { return(16); } };
    template <class x> struct B2<x*> { int getX() { return(20); } };
};

void main(int argc, char* argv[])
{
    B1<int>::B2<int>  a1;
    B1<int>::B2<int*> a2;
    printf("a1=%d, a2=%d.\r\n", a1.getX(), a2.getX());
}

テンプレート パラメータの名前はx両方のテンプレートにあり、コンパイラが混乱することはありません。2 番目の例は、コンパイラ クラッシュ (MSVC 2008) で失敗します。つまり、構文エラーは発生しません。

template <class x> struct B1
{
    template <class x> struct B2 { int getX() { return(16); } };
    template <class x> struct B2<x*>;
};

template <class x> template <class x>
struct B1<x>::B2<x*>
{
    int getX() { return(3); }
};

私の質問は、この例を修正する方法ではありません。これは明らかです。

テンプレート ヘッダーのパラメーターにアクセスする規則について標準 (C++2003) を調べましたが、関連するものが見つかりません。多分私は何かが欠けています。

の処理中B1<x>、コンパイラは最初のテンプレート ヘッダーのパラメータのみを考慮する必要がありますか? はい。では、処理中B2<x>に両方を考慮する必要がありますか? B1/B2 のパラメーターに修飾された識別子自体が含まれていて、これらの識別子がテンプレート ヘッダーのパラメーターにアクセスする必要がある場合は、より複雑なケースが特に興味深いものになります。簡単にするために、これの完全な例は示していません。

誰かがこれに遭遇し、コメントしたり、記事/本、YouTube ビデオなどを知っている場合は、ぜひ聞いてください.

更新:MSVCで次のことを試しました:

template <class x> struct B1
{
    x qq1;

    struct B2
    {
        int x;
    };
};

これはコンパイルされ、期待どおりに動作します。また、内部データ フィールドにアクセスする exe も試しましたx。これは、MS コンパイラが内部スコープで非表示のテンプレート パラメータを実装したことを示しています。これが標準に準拠していなくても、私にとっては論理的です。

4

1 に答える 1

4

質問はコメントによって回答されたと見なされていますが、以下にいくつかの詳細を提供します。私の答えはC++11(ISO / IEC 14882-2011)のみに基づいていることに注意してください。

パート1:テンプレートパラメーターをネストされたメンバーテンプレートのテンプレートパラメーターとして再利用できますか?

テンプレートパラメータのステータスに関する標準には2つの重要なステートメントがあります(具体的には、タイプパラメータ-質問に関連する唯一の種類です)。最初のステートメントは、それらがtypedef-namesと同じステータスを持っていると説明しています。

(§14.1/ 3)識別子が省略形に従わない型パラメータは、その識別子をテンプレートのスコープ内のtypedef-name(クラスまたはtypenameで宣言されている場合)またはtemplate-name(templateで宣言されている場合)として定義します宣言。[...]

そして、typedef-namesの可能な再宣言に関して、次のようになります。

(§7.1.3/ 6)特定のスコープでは、typedef指定子を使用して、そのスコープで宣言されたタイプの名前を再定義して、別のタイプを参照することはできません。[...]

(注:上記のルールはtypedef指定子の使用にのみ適用されるようですが、エイリアス宣言(§7.1.3/ 2)とテンプレートパラメーター宣言(§14.1/ 3)を使用してtypedef-namesを宣言することもできます。つまり、上記のルールは、同じスコープ内でtypedef-nameを再宣言するためのエイリアス宣言または実際のテンプレートパラメータの使用を明示的に除外していませんが、それは明らかに意図された意味です。言い回しは「typedef-name宣言なし」である必要があります。 「typedef指定子は使用しない」の代わりに「使用する」)。

これが意味することは、これを行うことができないということです。

{
  typedef int   T;
  typedef float T;
}

2番目の宣言は、最初に宣言されたのと同じスコープで発生するためTです。ただし、これは次のとおりです。

{
  typedef int   T;
  {
    typedef float T;
  }
}

T2番目の宣言はブロックスコープ内にあり(最初の宣言はまだ有効ですが)、最初に宣言されたスコープではないため、は完全に合法的に上記のルールに従いTます。

上で引用した§14.1/3のため、ルールはテンプレートパラメータ宣言にも適用されると想定する必要があります。

template <typename X> template <typename X>
struct Outer<X>::Inner<X> {

};

同じスコープ内で同じtypedef-nameを2回宣言することを意味するという単純な理由でさえ、違法です。

しかし、次のような場合

template <typename X>
struct Outer {

  template <typename X>
  struct Inner {
  };

};

template <typename X>の2番目の宣言はネストされたスコープに適用されると主張する人もいるかもしれません。幸い、この標準では、テンプレートパラメータのステータスに関する次の2番目のステートメントが提供されています。

(§14.6.1/ 6)テンプレートパラメータは、そのスコープ(ネストされたスコープを含む)内で再宣言してはなりません。テンプレートパラメータは、テンプレート名と同じ名前であってはなりません。[例:

template<class T, int i> class Y {
  int T; // error: template-parameter redeclared

  void f() {
    char T; // error: template-parameter redeclared
  }
};

template<class X> class X; // error: template-parameter redeclared

—終了例]

明確に述べられているように、宣言スコープ内の任意のtypedef-nameに適用される再宣言なしのルールは、テンプレートパラメーターの場合にもネストされたスコープに適用されます。

これは、ルールが実際に役立つと思う理由を動機付ける例です。検討:

template <typename T1>
struct Outer
{
  static const int outerID = 5;

  template <typename T2>
  struct Inner
  {
    int id1() { return Outer<T1>::outerID; }
    int id2() { return Outer::outerID; }
    int id3() { return outerID; }
  };
};

内部テンプレートの3つの関数はすべて、外部クラスの同じ静的メンバーを参照しますが、3つの異なる方法で参照されます。§14.6.2.1/4では、現在のインスタンス化を参照していると解釈される必要があるため、これをid2()行います。id3()Outer::outerIDouterIDOuter<T1>::outerID

内側のテンプレートのテンプレートパラメータを、外側のテンプレートとT1同じように、に置き換えると、意味id1()が変わります(内側のテンプレートでOuter<T1>の定義が何であれ、参照されるため)が、最も自然に–所属するテンプレートの現在のインスタンス化を引き続き参照してください。したがって、およびとは異なる値を返す可能性があり、これは最も厄介です。T1id2()id3()outerIDid1()id2()id3()

パート2:メンバーテンプレートの部分的な特殊化では、メンバーのテンプレートパラメーターを囲んでいるクラスのテンプレート引数として使用できますか?その逆も可能ですか?

質問によって対処される別の問題は、メンバーテンプレートの専門化またはクラス外の定義にあるかどうかです。

template <typename A> template <typename B>
struct Outer<A>::Inner<B> {
  // ...
};

外側のテンプレート(つまりこの場合)のテンプレート引数リストは、内側のテンプレート(つまり<A>この場合)に定義されたパラメーターを利用できB、その逆も可能です。

最初に、質問で与えられた2つのパラメーターが同一である特殊なケースについて考えてみましょう。

template <typename A> template <typename A>
struct Outer<A>::Inner<A> {
  // ...
};

再宣言の問題により、パート1でこれを除外しましたが、この構文の意図された意味を考慮することができます。つまり、内部クラステンプレートの明示的な特殊化を定義します。ここで、テンプレート引数は次のように想定されます。外側のテンプレートと同じです。構文的に正しい書き方は

template <typename A> template <>
struct Outer<A>::Inner<A> {
  // ...
};

つまり、2番目のパラメータリストは空になります。残念ながら、これはメンバーテンプレートの明示的な特殊化に相当します。これは、囲んでいるテンプレートも明示的に特殊化されていない限り違法です(§14.7.3/ 16)。

ただし、2つ以上のパラメーターを持つメンバーテンプレートの明示的な特殊化ではなく、部分的な特殊化を検討する場合、宣言は有効になります。

// Primary declaration
template <typename A>
struct Outer {
  template <typename A, typename B>
  struct Inner {
    // ...
  };
};

// Partial specialization of member template
template <typename A> template <typename B>
struct Outer<A>::Inner<B,A> {
  // ...
};

Aこれで、内部テンプレートの特殊な2番目の引数として、囲んでいるテンプレートのテンプレート引数を使用しました。外部クラスのテンプレート引数と内部クラスの2番目のテンプレート引数に同じデータ型を使用するテンプレートのインスタンス化。例:

Outer<int>::Inner<float,int> myvar;

上記で定義されたスペシャライゼーションをインスタンス化します。

したがって、メンバーテンプレートのテンプレート引数リストで囲んでいるクラスのテンプレートパラメータを使用しても問題はありません。その理由は、Inner<B,A>評価Aされるまでに、スコープレベルで定義されたtypedef-nameのステータスがすでにあるためですOuter

しかし、それを逆に行う、例えば

template <typename A> template <typename B>
struct Outer<B>::Inner<B,A> {
  // ...
};

はスコープBのtypedef-nameのみであるため、機能しません。Inner標準は次のように述べています。

(§14.5.2/ 1)[...]クラステンプレート定義の外部で定義されたクラステンプレートのメンバーテンプレートは、クラステンプレートのtemplate-parametersの後に、メンバーのtemplate-parametersを付けて指定する必要があります。レンプレート。[例:

    template<class T> struct string {
      template<class T2> int compare(const T2&);
      template<class T2> string(const string<T2>& s) { /∗ ... ∗/ }
    };
    template<class T> template<class T2> int string<T>::compare(const T2& s) {
    }

—終了例]

これは、2つのテンプレートパラメータリスト(一方の後にもう一方が続く)が別々に保持され、テンプレートパラメータの1つの結合リストとは見なされないことを意味すると解釈します。したがって、

template <typename A> template <typename B>
struct Outer<B>::Inner<B,A>

の解釈はOuter<B>2番目のテンプレートパラメータリストを利用できず、B未定義になりますが、前のケースでは

template <typename A> template <typename B>
struct Outer<A>::Inner<B,A>

の一部であるスコープで宣言されたtypedef-nameのステータスがあるため、の解釈Inner<B,A>が可能です。つまり、と同じように正常に解釈されます。AInnerInner<B,Outer::A>Inner<B,Outer<A>::A>

于 2012-07-07T09:41:46.293 に答える