「テンプレート」と「タイプ名」のキーワードをどこに、なぜ配置しなければならないのですか?


1126

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


1
迷惑な質問:boost :: Variantをブーストしないのはなぜですか?
Assaf Lavie

58
政治的感受性、移植性。
MSalters 2009年

5
最後の質問とコードを最初に配置し、コードを水平方向に1024x画面に収まるように短くして、実際の質問(「テンプレート/タイプ名をどこに置くか」)を目立たせました。
ヨハネスシャウブ-litb 2010

7
「typename」と「template」について不思議に思うほとんどの人は「依存名」が何であるかを知らないように見えるため、タイトルから「依存名」を削除しました。このようにすると、混乱が少なくなります。
Johannes Schaub-litb

2
@MSalters:boostは非常に移植可能です。ブーストが受け入れられないことが多いのは、政治だけが一般的な理由だと思います。私が知っている唯一の良い理由は、ビルド時間の増加です。それ以外の場合は、何千ドルも失うことで車輪が再発明されます。
v.oddou 2013年

回答:


1164

私のC ++ 11の答えについてはこちらも参照してください)

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

t * f;

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

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

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

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

ユーザーがテンプレートをインスタンス化するまで待って、後での本当の意味を見つけましょうt::x * f;

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

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

「typename」キーワード

答えは、コンパイラがこれをどのように解析するか決定することです。t::xが依存名である場合typename、コンパイラに特定の方法で解析するように指示するために、プレフィックスを付ける必要があります。標準は(14.6 / 2)で述べています:

テンプレートの宣言または定義で使用され、template-parameterに依存する名前は、適切な名前の検索でタイプ名が見つからないか、名前がキーワード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、修飾された名前の前のみを許可します -したがって、修飾されていない名前は、そうする場合に常に型を参照することがわかっていると見なされます。

導入テキストで示唆されているように、テンプレートを示す名前にも同様の落とし穴が存在します。

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

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

boost::function< int() > f;

それは人間の読者には明白に見えるかもしれません。コンパイラはそうではありません。次のboost::functionandの任意の定義を想像してください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

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

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 +and operator 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 
     { };
  • 宣言の使用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
     };

22
この回答は、以前に削除したFAQエントリからコピーしたものです。回答するためだけに、新しい「疑似質問」を作成するのではなく、既存の同様の質問を使用するほうがよいことがわかりました。最後の部分(typename / templateが禁止されている場合)のアイデアを編集して@Prasoonに回答していただき、ありがとうございます。
ヨハネスシャウブ-litb 2010年

1
この構文をいつ使用すればよいですか?this-> template f <int>(); 私はこのエラーを受け取ります(明確化として)はテンプレート内でのみ許可されていますが、templateキーワードなしでは問題なく機能します。
バルキ

1
今日、同様の質問をしましたが、すぐに重複としてマークされました:stackoverflow.com/questions/27923722/…。新しい質問を作成する代わりに、この質問を復活させるように指示されました。それらが重複していることに同意しないと言わなければなりませんが、私は誰ですか?では、typenameこの時点で構文がタイプ名以外の代替解釈を許可していない場合でも、強制される理由はありますか?
JorenHeit

1
@Pabloあなたは何も見逃していません。しかし、完全な行があいまいにならない場合でも、あいまいさをなくす必要があります。
Johannes Schaub-litb

1
@Pabloの目的は、言語とコンパイラをよりシンプルに保つことです。キーワードを必要とする頻度を減らすために、より多くの状況で自動的に状況を把握できるようにする提案があります。この例では、トークンあいまいであり、ダブルの後に「>」が表示された後にのみ、テンプレートの山括弧として曖昧さをなくすことができます。詳細については、C ++コンパイラのパーサーを実装した経験がないため、私は尋ねるのは間違っています。
Johannes Schaub-litb

136

C ++ 11

問題

C ++ 03のルールは、必要typenametemplate大部分が妥当である時期に関するものですが、その定式化には1つの厄介な欠点があります。

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

図から分かるように、私たちも、コンパイラは、それ自体アウト完璧図できれば曖昧さ回避のキーワードを必要とするA::result_typeだけであることができint(ひいてはタイプである)、そしてthis->g唯一のメンバーテンプレートができgたとしても(後に宣言したAであろうと、明示的にどこかに特化していますそのテンプレート内のコードには影響しないので、その意味は後の特殊化によって影響を受けることはありませんA!を。

現在のインスタンス化

状況を改善するために、C ++ 11では、型が囲んでいるテンプレートを参照するときに言語が追跡されます。それを知るために、型が独自の名前である名前の特定の形態、(上記でを用いて形成されている必要がありAA<T>::A<T>)。そのような名前で参照される型は、現在のインスタンス化であることがわかっています。名前が形成されるタイプがメンバー/ネストされたクラスである場合、すべての現在のインスタンスである(その後、複数の種類が存在してもよいA::NestedClassし、A両方の現在のインスタンスです)。

この考え方に基づき、言語はそれを言うCurrentInstantiation::FooFooそしてCurrentInstantiationTyped->Foo(などA *a = this; a->Fooすべてです)現在のインスタンスのメンバー ならば、彼らはただ行うことで、現在のインスタンスまたはその非依存の基底クラスの1つであるクラスのメンバー(であることが判明しています名前をすぐに検索します)。

キーワードtypenametemplateは、修飾子が現在のインスタンス化のメンバーである場合は不要になりました。ここで覚えておくべき重要な点A<T>は、それがまだ型に依存する名前であることです(結局のところ、型にTも依存しています)。しかしA<T>::result_type、型であることが知られています-コンパイラは、この種類の依存型を「魔法のように」調べてこれを理解します。

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

それは印象的ですが、もっとうまくできるでしょうか?言語はさらに進んで、インスタンス化時に実装が再度ルックアップすることを要求D::result_typeますD::f(たとえ定義時にすでにその意味を見つけたとしても)。ルックアップ結果が異なる場合やあいまいな結果となった場合、プログラムの形式が正しくなく、診断を行う必要があります。Cこのように定義するとどうなるか想像してみてください

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

をインスタンス化するときにコンパイラがエラーをキャッチする必要がありますD<int>::f。解放し、あなたのことを、あなたが依存基底クラスとのトラブルに巻き込まれることができれば、あなたを守るルックアップする「遅延」、また、「即時」参照:二つの世界の最高を取得し、あなたはそうtypenametemplate

不明な専門分野

のコードでDは、名前typename D::questionable_typeは現在のインスタンス化のメンバーではありません。代わりに、言語はそれを未知の専門分野のメンバーとしてマークします。特に、これは常に、あなたがやっている場合であるDependentTypeName::FooDependentTypedName->Fooのどちらかに依存するタイプがありません現在のインスタンス化(コンパイラはあきらめ、私たちが後で何を見ていきます」と言うことができ、その場合にはFooある)、またはそれがある現在のインスタンス化と名前またはその非依存基本クラスに名前が見つかりませんでした。依存基本クラスもあります。

h上記で定義されたAクラステンプレート内にメンバー関数があるとどうなるか想像してみてください

void h() {
  typename A<T>::questionable_type x;
}

C ++ 03では、インスタンス化する有効な方法A<T>::h(引数に何を指定してもT)があり得ないため、言語はこのエラーをキャッチできました。C ++ 11では、コンパイラーがこのルールを実装するためのより多くの理由を与えるために、言語にさらにチェックがあります。にAは依存基本クラスがなく、member A宣言されていないquestionable_typeため、名前A<T>::questionable_typeは現在のインスタンス化のメンバーでもなければ不明な専門分野のメンバー。その場合、そのコードがインスタンス化時に有効にコンパイルされる方法はないはずです。そのため、言語は、修飾子が現在のインスタンス化である名前が、不明な特殊化のメンバーでも現在のインスタンス化のメンバーでもないことを禁止しています(ただし、 、この違反はまだ診断する必要はありません)。

例と雑学

あなたはこの答えでこの知識を試し、上記の定義が実際の例であなたにとって意味があるかどうかを見ることができます(それらはその答えで少し詳細が繰り返されています)。

C ++ 11の規則により、次の有効なC ++ 03コードの形式が正しくありません(C ++委員会は意図していませんが、おそらく修正されません)。

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

この有効なC ++ 03コードはインスタンス化時にバインドthis->fA::fれ、すべて問題ありません。ただし、C ++ 11はそれをすぐにバインドしB::f、インスタンス化するときに、ルックアップが依然として一致するかどうかを確認するためのダブルチェックを必要とします。ただし、をインスタンス化するC<A>::gと、支配ルールが適用され、A::f代わりにルックアップが検索されます。


fyi-この回答はここで参照されています:stackoverflow.com/questions/56411114/… この回答のコードの多くは、さまざまなコンパイラでコンパイルできません。
Adam Rackis

@AdamRackisは、C ++仕様が2013年以降変更されていないと想定し(この回答を書いた日付)、コードを試したコンパイラはまだこのC ++ 11 +機能を実装していません。
ヨハネスシャウブ-litb

100

序文

この投稿は、litbの投稿読みやすくしたものです。

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


typenameand の目的は何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

異なるTsは、関連するセマンティクスを大幅に変更する可能性があります。

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)は代わりに123と既に宣言された変数xの積を計算する式で構成されます。



理論的根拠

C ++標準は、少なくともこの場合、私たちの安全と幸福を気にします。

実装が厄介な驚きに見舞われる可能性を防ぐために、標準では、名前をtype-nameまたはテンプレートとして扱いたい場所に意図を明示的に示すことにより、依存名の曖昧さを整理することを義務付けていますid

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



従属名を処理する方法は

これがハリウッド映画である場合、扶養家族の名前は、身体との接触を通じて広がる病気であり、ホストを混乱させて混乱させます。混乱を招く可能性があり、形式が正しくない、erhm ..プログラムにつながる可能性があります。

依存-nameはあるいかなる直接的、または間接的に依存名前のテンプレートパラメータ

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
    • テンプレートIDである「NestedTrait」は、に依存しSomeTrait<T>ます。
    • F)の最後の「タイプ」NestedTraitに依存しSomeTrait<T>ます。これはに依存します。
  • G
    • メンバー関数テンプレートのように見える"data"はfooのタイプがのインスタンス化に依存するため、間接的に依存名です。SomeTrait<T>

コンパイラーが従属名を変数/関数として解釈する場合、ステートメント(E)、(F)、または(G)のいずれも無効です(前に述べたように、明示的に別段の指定がない場合に起こります)。

ソリューション

g_tmpl有効な定義を作成するには、型(E)、テンプレートIDイン(F)、テンプレートIDG)が必要であることをコンパイラに明示的に通知する必要があります。

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前に直接適用することを意味します。



CAN I JUST STICK THE KEYWORDS任意の名前のフロント?

缶私は固執typenameしてtemplate...任意の名前の前に私は、彼らが表示されるコンテキストを心配する必要はありません?」 -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 */ {
       ...
     };


  • 場合は、テンプレート-idが派生クラスの中で言及されているものです使用して、ディレクティブ

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

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を見てください


たとえば、Union <float、bool> :: operator =(U)をU == intで呼び出そうとすると、inUnion <U>がインスタンス化されます。プライベートセット(U、inUnion <U> * = 0)を呼び出します。
MSalters 2009年

そして、result = true / falseでの作業は、boost :: enable_if <>が必要になることです。これは、現在のOSXツールチェーンと互換性がありません。ただし、別のテンプレートを使用することをお勧めします。
MSalters 2009年

Lucはtypedef Tail :: inUnion <U>ダミーを意味します。ライン。Tailがインスタンス化されます。inUnion <U>ではありません。完全な定義が必要なときにインスタンス化されます。たとえば、sizeofを取得したり、(:: fooを使用して)メンバーにアクセスしたりすると発生します。@MSaltersとにかく、別の問題があります:
Johannes Schaub-litb 2009年

-sizeof(U)が負になることは決してありません:) size_tは符号なし整数型であるためです。あなたはいくつかの非常に高い数を取得します。あなたはおそらくsizeof(U)> = 1を実行したいですか?-1:1または同様:)
Johannes Schaub-litb 2009年

私はそれを未定義のままにし、宣言するだけです:template <typename U> struct inUnion; したがって、インスタンス化することはできません。sizeofを指定すると、コンパイラはインスタンス化しなくてもエラーが発生する可能性があります。なぜなら、sizeof(U)が常に> = 1であることがわかっていれば、...
Johannes Schaub-litb

20

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


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

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


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キーワードは、それが型の名前であることを指定します。


概要

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.