1. ホーム
  2. c++

[解決済み] template "と "typename "キーワードはどこに、なぜ入れなければならないのですか?

2022-03-19 02:33:43

質問

テンプレートでは、どこに、なぜ typenametemplate を従属名で使うか?
そもそも従属名とは何ですか?

私は以下のようなコードを持っています。

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

私が抱えている問題は typedef Tail::inUnion<U> dummy という行があります。私は、間違いなく inUnion は従属名であり、VC++がそれを詰問するのは全く正しいことです。
を追加すればいいことも知っています。 template inUnionがtemplate-idであることをコンパイラに伝えるために、どこかにあるはずです。しかし、具体的にはどこなのでしょうか?また、inUnionがクラステンプレートであると仮定すべきなのだろうか? inUnion<U> は関数ではなく型を指定しているのでしょうか?

どのように解決するのですか?

(参照 C++11の回答はこちら )

C++のプログラムを解析するために、コンパイラはある名前が型であるかどうかを知る必要があります。次の例は、そのことを示している。

t * f;

これはどのようにパースされるべきなのでしょうか?多くの言語では、コンパイラは解析のために名前の意味を知る必要はなく、基本的にコード行がどのような動作をするかを知っています。しかしC++では、上記のように t という意味です。もしそれが型であれば、ポインタの宣言になります f . しかし、それが型でない場合は、乗算になります。そこで、C++規格では、(3/7)段落でこう言っている。

名前には、型やテンプレートを表すものがあります。一般に、ある名前に遭遇した場合、その名前を含むプログラムの解析を続行する前に、その名前がこれらのエンティティのいずれかを示すかどうかを判断する必要があります。これを判断する処理を名前検索と呼びます。

コンパイラはどのようにして、ある名前 t::x が参照する場合 t は、テンプレート型パラメータを参照していますか? x は、乗算される静的な int データメンバかもしれませんし、同様に、宣言に降伏するネストされたクラスやtypedefの可能性もあります。 もしある名前がこの性質、つまり実際のテンプレート引数がわかるまで調べられないという性質を持つなら、それは 従属名 (テンプレート・パラメータに依存する)。

ユーザーがテンプレートをインスタンス化するまで待機することをお勧めします。

ユーザがテンプレートをインスタンス化するまで待って、後で t::x * f; .

これは動作しますし、実際、可能な実装方法として標準でも認められています。これらのコンパイラは基本的にテンプレートのテキストを内部バッファにコピーし、インスタンス化が必要なときだけ、テンプレートをパースし、場合によっては定義のエラーを検出するのです。しかし、テンプレートの作者によるエラーでテンプレートのユーザ(かわいそうな同僚!)を悩ませる代わりに、他の実装では、早い段階でテンプレートをチェックし、インスタンス化が行われる前に、できるだけ早く定義にエラーを与えることを選択します。

だから、ある名前は型であり、ある名前は型でないとコンパイラに伝える方法が必要なのです。

typename"キーワード

という答えが返ってきます。 私たち は、コンパイラがこれをどのようにパースするかを決める。もし t::x が従属名である場合、その前に typename を使用して、コンパイラに特定の方法でパースするように指示します。規格では(14.6/2)でこう言っています。

テンプレートの宣言または定義で使用され、テンプレート・パラメータに依存する名前は、テンプレート・パラメータに依存します。 名前検索で型名が見つかるか、その名前が修飾されていない限り、型名ではないものとみなされます。 キーワードtypenameによって。

という名前がたくさんあります。 typename なぜなら、コンパイラはテンプレート定義にある適切な名前のルックアップを使えば、その構成要素をどのようにパースすればよいかを自ら見つけ出すことができるからです。 T *f; の場合 T はタイプ・テンプレート・パラメータです。しかし t::x * f; を宣言とするならば、次のように書かなければなりません。 typename t::x *f; . キーワードを省略した場合,非型とみなされる名前でも,インスタンス化したときに型を表すとわかると,コンパイラから通常のエラーメッセージが出されます.また、定義時にエラーメッセージが表示されることもあります。

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

この構文では typename 修飾名の前にのみ - そのため、非修飾名が型を参照していることは常に知られているものとみなされます。

紹介文にあるように、テンプレートを示す名前にも同様の問題がある。

template"キーワード

上記の最初の引用と、Standardがテンプレートについても特別な処理を要求していることを思い出してください。次のような無難な例を見てみましょう。

boost::function< int() > f;

人間が読むと当たり前に見えるかもしれません。しかし、コンパイラにとってはそうではない。次のような任意の定義を想像してみてください。 boost::functionf :

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

これは実際に有効な 表現 ! これは、小なり演算子を使って boost::function に対して、ゼロ( int() という演算子で比較し、その結果得られた bool に対して f . しかし、皆さんもよくご存知のように boost::function 現実には はテンプレートなので、コンパイラは知っています(14.2/3)。

名前検索(3.4)でテンプレート名と判定された後、その名前の後に<が続く場合、<は、テンプレート名と判定されます。 は常にテンプレート引数リストの先頭とみなされ,名前に続いて小文字が続くことはない。 演算子を使用します。

と同じ問題に戻ってしまいます。 typename . もし、コードを解析するときに、その名前がテンプレートであるかどうかがまだわからないとしたらどうでしょうか?その場合は template で指定されるように、テンプレート名の直前で 14.2/4 . これは次のようになります。

t::template f<int>(); // call a function template

テンプレート名は :: の後にもあります。 -> または . をクラスメンバーアクセスの中で使用することができます。ここにもキーワードを挿入する必要があります。

this->template f<int>(); // call a function template


依存関係

標準規格の分厚い本を持っていて、私が何を言っていたのか知りたい人のために、標準規格でどのように規定されているのかについて少しお話しします。

テンプレート宣言の中には、テンプレートをインスタンス化する際に使用するテンプレート引数によって、異なる意味を持つ構成要素があります。式は異なる型や値を持ち、変数は異なる型を持ち、関数呼び出しは最終的に異なる関数を呼び出すかもしれません。このような構成要素は一般に次のように言われます。 依存 をテンプレート・パラメータに追加します。

この規格では、ある構成要素が依存するかどうかのルールを正確に定義しています。また、論理的に異なるグループに分割しています。ひとつは型を、もうひとつは式を捕捉します。式は、その値や型に依存することがあります。そこで、典型的な例を添付しておきます。

  • 依存する型(例:型テンプレートパラメータ T )
  • 値依存の式(例:型によらないテンプレートパラメータ N )
  • 型依存の式(例:型テンプレートパラメータへのキャスト (T)0 )

ほとんどのルールは直感的に理解でき、再帰的に構築される。例えば、以下のように構成された型は T[N] は、以下の場合に従属型となります。 N が値依存の式であるか T は従属型です。この詳細については、セクション (14.6.2/1 )の依存型について説明します。 (14.6.2.2) は型依存の式、そして (14.6.2.3) は値依存の式である。

従属的な名前

この規格では まさに 従属名 . 単純に読めば(ご存じ、最小驚嘆の原則)、それが定義しているのはすべて 従属名 は、以下の関数名に関する特殊なケースです。しかし、明らかに T::x もインスタンス化コンテキストで検索する必要があり、それも従属名である必要があります(幸いなことに、C++14の中間時点で、委員会はこの混乱した定義を修正する方法について検討を開始しました)。

この問題を回避するために、私はStandardのテキストを単純に解釈することに頼りました。依存する型や式を表すすべての構成要素のうち、そのサブセットは名前を表しています。したがって、それらの名前は従属名と呼ばれる。名前はさまざまな形をとることができる-とStandardは言っている。

<ブロッククオート

名前とは,識別子(2.11),演算子関数ID(13.5),変換関数ID(12.3.2)又はテンプレートID(14.2)を用いて,実体又はラベル(6.6.4, 6.1) を表すものである。

識別子は単なる文字/数字の羅列ですが、次の2つは operator +operator type のフォームを使用します。最後の形式は template-name <argument list> . これらはすべて名前であり、Standardの慣例では、名前には、その名前がどの名前空間またはクラスで検索されるべきかを示す修飾子を含めることができる。

値依存の表現 1 + N は名前ではありませんが N があります。名前であるすべての従属構文の部分集合は、以下のように呼ばれます。 従属名 . しかし、関数名はテンプレートのインスタンスによって異なる意味を持つことがありますが、残念ながらこの一般的なルールには当てはまりません。

依存する関数名

この記事の主な関心事ではありませんが、言及する価値はあります。関数名は例外で、別に扱われます。識別子の関数名は、それ自体ではなく、呼び出しの際に使用される型依存の引数式に依存します。この例では f((T)0) , f は従属名である。規格では、これは (14.6.2/1) .

その他の注意事項および例

の両方が必要な場合があります。 typenametemplate . あなたのコードは次のようになります。

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

キーワード template は、必ずしも名前の最後の部分に表示される必要はありません。次の例のように、スコープとして使用されるクラス名の前に表示することができます。

typename t::template iterator<int>::value_type v;

場合によっては、以下のようにキーワードが禁止されることがあります。

  • 依存する基底クラスの名前には、以下のような記述はできません。 typename . 与えられた名前は、クラスの型名であることが前提です。これは、ベースクラスリストとコンストラクタ初期化子リストの両方の名前に当てはまります。

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
    
  • using-declarations の中では template の後、最後の :: と、C++委員会 という 解決策に取り組むのではなく

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };