1276

typenameテンプレートでは、template依存する名前を配置する必要がある場所と理由は?
とにかく、従属名とは正確には何ですか?

次のコードがあります。

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++ はそれを窒息させているのはまったく正しいことです。また、inUnion がテンプレート ID であることをコンパイラーに伝えるためにどこかに
追加できるはずであることもわかっています。templateしかし、正確にはどこですか?そして、inUnion がクラス テンプレートである、つまりinUnion<U>、関数ではなく型の名前を付けていると想定する必要がありますか?

4

8 に答える 8

1311

私のC ++ 11の回答についてはこちらもご覧ください)

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

t * f;

これはどのように解析する必要がありますか? 多くの言語では、コンパイラは解析のために名前の意味を知る必要はなく、基本的にコード行が実行するアクションを認識します。ただし、C++ では、上記は意味に応じて大きく異なる解釈をもたらす可能性がありtます。型の場合は、ポインタの宣言になりますf。ただし、型でない場合は乗算になります。したがって、C++ 標準は段落 (3/7) で次のように述べています。

一部の名前は、タイプまたはテンプレートを示します。一般に、名前が検出されるたびに、名前を含むプログラムの解析を続行する前に、その名前がこれらのエンティティのいずれかを示しているかどうかを判断する必要があります。これを決定するプロセスは、名前検索と呼ばれます。

テンプレート型パラメータを参照しているt::x場合、コンパイラは名前が何を参照しているかをどのように見つけますか? 乗算可能な静的 int データ メンバーである可能性もあれば、宣言を生成できるネストされたクラスまたは typedef である可能性もあります。名前にこのプロパティ (実際のテンプレート引数が判明するまで検索できない) がある場合、その名前は依存名と呼ばれます (テンプレート パラメーターに「依存」します)。tx

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

ユーザーがテンプレートをインスタンス化するまで待ってから、後で の本当の意味を見つけてt::x * f;ください。

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

したがって、特定の名前は型であり、特定の名前は型ではないことをコンパイラーに伝える方法が必要です。

「typename」キーワード

答えは:コンパイラがこれを解析する方法決定します。t::x依存名の場合typename、特定の方法で解析するようにコンパイラに指示するために、接頭辞 by を付ける必要があります。標準は (14.6/2) で次のように述べています。

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

必要のない名前がたくさんあります。コンパイラは、テンプレート定義で適切な名前の検索を使用して、typename構成体自体を解析する方法を見つけ出すことができるためです。しかし、が宣言であるためには、 のように書かなければなりません。キーワードを省略し、名前が非型であると見なされた場合、インスタンス化によってそれが型を示していることが判明すると、通常のエラー メッセージがコンパイラによって出力されます。その結果、定義時にエラーが発生する場合があります。T *f;Tt::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修飾名の前にのみ許可されます。したがって、修飾されていない名前が型を参照する場合、型を参照することが常に知られていることが認められていると見なされます。

紹介テキストで示唆されているように、テンプレートを表す名前にも同様の落とし穴があります。

「テンプレート」キーワード

上記の最初の引用と、標準がテンプレートに対しても特別な処理を必要としている方法を覚えていますか? 次の無害に見える例を見てみましょう。

boost::function< int() > f;

人間の読者には明白に見えるかもしれません。コンパイラの場合はそうではありません。と の次の任意の定義を想像してboost::functionくださいf

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

それは実際には有効なです。小なり演算子を使用してboost::functionゼロ ( int()) と比較し、大なり演算子を使用して結果を と比較boolfます。ただし、ご存知かもしれませんがboost::function 、実際にはテンプレートであるため、コンパイラーは次のことを認識しています (14.2/3):

名前検索 (3.4) で名前がテンプレート名であることが判明した後、この名前の後に < が続く場合、< は常にテンプレート引数リストの先頭と見なされ、決して名前の後に less- が続くものとして見なされません。オペレーターより。

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

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

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

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

依存関係

分厚い標準の本を棚に置いていて、私が何について話しているのか正確に知りたい人のために、これが標準でどのように指定されているかについて少し話します。

テンプレート宣言では、テンプレートのインスタンス化に使用するテンプレート引数に応じて、いくつかの構成要素の意味が異なります。式は異なる型または値を持つ場合があり、変数は異なる型を持つ場合があるか、関数呼び出しが異なる関数を呼び出すことになる場合があります。このような構成は、一般に、テンプレート パラメーターに依存すると言われています。

標準では、構造が依存しているかどうかによって規則が正確に定義されています。それらを論理的に異なるグループに分けます。1 つは型をキャッチし、もう 1 つは式をキャッチします。式は、その値や型に依存する場合があります。したがって、典型的な例を追加すると、次のようになります。

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

ほとんどのルールは直感的で、再帰的に構築されます。たとえば、値依存式または依存型でT[N]ある場合、依存型として構築された型です。これの詳細は、依存型、型依存式、および値依存式のセクションで読むことができます。NT(14.6.2/1(14.6.2.2)(14.6.2.3)

従属名

標準では、従属名が正確に何であるかについて少し不明確です。単純な読み方 (ご存知のように、最小の驚きの原則) では、従属名として定義されているのは、以下の関数名の特殊なケースだけです。しかし、明らかにインスタンス化のコンテキストでも検索する必要があるため、従属名である必要もあります (幸いなことに、C++14 の中頃から、委員会はこの紛らわしい定義を修正する方法を検討し始めています)。T::x

この問題を回避するために、私は標準テキストの単純な解釈に頼っています。依存する型または式を表すすべての構造のうち、それらのサブセットは名前を表します。したがって、これらの名前は「従属名」です。名前はさまざまな形式を取ることができます-標準は次のように述べています:

名前は、エンティティまたはラベル (6.6.4、 6.1)

識別子は単純な文字/数字のシーケンスですが、次の 2 つはoperator +andoperator type形式です。最後の形はtemplate-name <argument list>. これらはすべて名前であり、標準での従来の使用により、名前には名前を検索する名前空間またはクラスを示す修飾子を含めることもできます。

値依存式1 + Nは名前ではなく、名前Nです。名前であるすべての依存構造のサブセットは、依存名と呼ばれます。ただし、関数名は、テンプレートの異なるインスタンス化では異なる意味を持つ場合がありますが、残念ながら、この一般的な規則には当てはまりません。

依存関数名

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

追加の注意事項と例

typename多くの場合、との両方が必要templateです。コードは次のようになります

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 宣言で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
     };
    
于 2009-03-05T00:27:57.213 に答える
119

序文

この投稿は、 litb の投稿に代わる読みやすいものとなることを目的としています。

根本的な目的は同じです。「いつ?」の説明 なぜ?" 適用する必要がありますtypenametemplate


typenameとの目的は何templateですか?

typenameテンプレートをtemplate宣言するとき以外の状況で使用できます。

C++には、名前の処理方法をコンパイラに明示的に指示する必要がある特定のコンテキストがあり、これらすべてのコンテキストには 1 つの共通点があります。少なくとも 1 つのtemplate-parameterに依存します。

解釈が曖昧な場合は、次のように呼びます。「従属名」。

この投稿では、 dependent-namesと 2 つのキーワードの関係について説明します。


スニペットは 1000 語を超えています

次のfunction-templateで何が起こっているかを、自分自身、友人、またはおそらくあなたの猫に説明してみてください。( A )でマークされたステートメントで何が起こっているか?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


考えているほど簡単ではないかもしれません。より具体的には、 ( A ) を評価した結果は、 template-parameter として渡される型の定義に大きく依存Tします。

が異なるTと、関連するセマンティクスが大幅に変わる可能性があります。

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


2 つの異なるシナリオ:

  • ( C ) のように、型Xで関数テンプレートをインスタンス化すると、 xという名前の int へのポインターの宣言ができます。

  • ( D ) のようにY型でテンプレートをインスタンス化すると、 ( A ) は代わりに、既に宣言されている変数xを掛けた123の積を計算する式で構成されます。



理論的根拠

C++ 標準は、少なくともこの場合、私たちの安全と福利に気を配っています。

実装が厄介な驚きに苦しむ可能性を防ぐために、標準では名前をtype - nameまたはtemplate- id .

何も指定されていない場合、dependent-nameは変数または関数のいずれかと見なされます。



従属名の処理方法

もしこれがハリウッド映画だったら、従属名は体の接触によって広がり、即座に宿主に影響を与えて混乱させる病気になる. おそらく、不適切な形の個人プログラムにつながる可能性のある混乱。

dependent-nameは、 template-parameterに直接的または間接的に依存する任意名前です。

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

上記のスニペットには、4 つの従属名があります。

  • E )
    • 「タイプ」は、のインスタンス化に依存します。これには、およびSomeTrait<T>が含まれます。T
  • F )
    • template-idである"NestedTrait"SomeTrait<T>は、 andに依存します。
    • ( F )の最後にある"type"は、 に依存するNestedTraitに依存しSomeTrait<T>ます。
  • G )
    • メンバ関数 template のように見える"data"は、 fooの型がのインスタンス化に依存するため、間接的に依存名です。SomeTrait<T>

コンパイラーが依存名を変数/関数として解釈する場合、ステートメント ( E )、( F )、または ( G ) のいずれも有効ではありません (前述のように、明示的に別のことを言わない場合に起こることです)。

ソリューション

有効な定義を作成するには、( E )の、( F )のtemplate-idと型、 ( G ) のtemplate-idg_tmplを期待することをコンパイラに明示的に伝える必要があります。

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

名前が型を表すたびに、関係するすべての名前はtype-namesまたはnamespacesいずれかでなければなりません。これを念頭に置いて、完全修飾名の先頭に適用することは非常に簡単です。 typename

templateただし、次のような結論に達する方法がないため、この点で異なります。「ああ、これはテンプレートです。それなら、この他のものもテンプレートでなければなりません」 . これは、そのように扱いたい名前templateの前に直接適用することを意味します。



名前の前にキーワードを付けることはできますか?

"名前の前にandを付けてもいいですか?それらが表示されるコンテキストについて心配したくありません...typenametemplate " -Some C++ Developer

標準の規則では、修飾名( K )を扱っている限りキーワードを適用できると述べていますが、名前が修飾されていない場合、アプリケーションの形式は正しくありません ( L )。

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

: 適用するtypename、または適用するtemplate必要のない状況で使用することは、適切な方法とは見なされません。何かができるからといって、そうすべきだという意味ではありません。


typenameさらに、とtemplate明示的に禁止されているコンテキストがあります。

  • クラスが継承するベースを指定する場合

    派生クラスのbase-specifier-listに記述されたすべての名前は、すでにtype-nameとして扱われており、明示的に指定することは形式typenameが正しくなく、冗長でもあります。

                        // .------- the base-specifier-list
      template<class T> // v
      struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
        ...
      };
    

  • template-idが派生クラスのusing-directiveで参照されている場合

      struct Base {
        template<class T>
        struct type { };
      };
    
      struct Derived : Base {
        using Base::template type; // ill-formed
        using Base::type;          // legal
      };
    
于 2014-06-07T20:28:32.803 に答える
29

この回答は、タイトルの質問 (の一部) に回答するためのかなり短くて甘いものであることを意図しています。それらをそこに置く必要がある理由を説明する詳細な回答が必要な場合は、ここにアクセスしてください


キーワードを配置するための一般的なルールtypenameは、ほとんどの場合、テンプレート パラメーターを使用していて、ネストされたtypedefエイリアスまたは using-alias にアクセスする場合です。次に例を示します。

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

これは、メタ関数や一般的なテンプレート パラメーターを受け取るものにも適用されることに注意してください。ただし、指定されたテンプレート パラメーターが明示的な型である場合は、次のように指定する必要はありませんtypename

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

修飾子を追加するための一般的な規則はtemplate、通常、それ自体がテンプレート化されている構造体/クラスのテンプレート化されたメンバー関数 (静的またはその他) を含むことを除いて、ほとんど同じです。次に例を示します。

この構造体と関数が与えられた場合:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

関数内からアクセスしようとするとt.get<int>()、エラーが発生します。

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

templateしたがって、このコンテキストでは、事前にキーワードが必要であり、次のように呼び出します。

t.template get<int>()

そうすれば、コンパイラはこれを ではなく適切に解析しますt.get < int

于 2014-06-06T22:23:21.637 に答える
20
typedef typename Tail::inUnion<U> dummy;

ただし、inUnion の実装が正しいかどうかはわかりません。私が正しく理解していれば、このクラスはインスタンス化されていないため、「失敗」タブは絶対に失敗しません。型が共用体にあるかどうかを単純なブール値で示す方がよいかもしれません。

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Boost::Variantを見てください。

PS2:特に Andrei Alexandrescu の本: Modern C++ Design のtypelistsを見てください。

于 2009-03-04T13:37:47.963 に答える
2

cplusplus.comからの同様の質問に対するJLBorges の優れた回答をそのまま掲載します。これは、この件に関して私が読んだ中で最も簡潔な説明です。

私たちが作成するテンプレートには、従属名と非従属名という 2 種類の名前を使用できます。依存名は、テンプレート パラメータに依存する名前です。非依存の名前は、テンプレート パラメーターが何であるかに関係なく、同じ意味を持ちます。

例えば:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)
      
    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

従属名が参照するものは、テンプレートのインスタンス化ごとに異なる可能性があります。その結果、C++ テンプレートは「2 フェーズの名前検索」の対象になります。テンプレートが最初に解析されるとき (インスタンス化が行われる前)、コンパイラは非依存の名前を検索します。テンプレートの特定のインスタンス化が行われると、それまでにテンプレート パラメーターが認識され、コンパイラーは従属名を検索します。

最初のフェーズで、パーサーは依存名が型の名前か非型の名前かを知る必要があります。デフォルトでは、従属名は非タイプの名前であると見なされます。従属名の前の typename キーワードは、それが型の名前であることを指定します。


概要

キーワード typename は、型を参照し、テンプレート パラメーターに依存する修飾名がある場合にのみ、テンプレートの宣言と定義で使用します。

于 2018-08-11T12:20:50.773 に答える
1

従属名は、テンプレート パラメータに依存する名前です。実際にそれらを開始する前に、テンプレート クラス/関数を適切にコンパイルするようにコンパイラに指示する必要があります。

  • typename -> 従属名が実際の型であることをコンパイラに伝える

    template <class T>
    struct DependentType
    {
      typename T::type a;
      using Type=typename T::type;
    };
    
    
  • テンプレート -> 依存する名前がテンプレート関数/クラスであることをコンパイラに伝える

    template <class T>
    struct DependentTemplate
    {
      // template function
      template <class U>
      static void func() {}
    
      // template class
      template <class U>
      struct ClassName{};
    };
    
    
    template <class T1, class T2>
    void foo()
    {
      // 3 ways to call a dependent template function
      DependentTemplate<T1>::template func<T2>();
      DependentTemplate<T1>().template func<T2>();
      (new DependentTemplate<T1>())->template func<T2>();
    
      // You need both typename and template to reference a dependent template class
      typename DependentTemplate<T1>::template ClassName<T2> obj;
      using Type=typename DependentTemplate<T1>::template ClassName<T2>;
    }
    
于 2021-05-15T00:07:02.690 に答える