web-dev-qa-db-ja.com

"template"キーワードと "typename"キーワードをどこに、なぜ配置する必要があるのですか?

テンプレートでは、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++はそれをチョークするのに全く正しいです。また、inUnionがtemplate-idであることをコンパイラに伝えるために、どこかにtemplateを追加することができるはずです。しかし、正確にはどこ?そしてそれから、inUnionはクラステンプレートである、すなわちinUnion<U>は型であって関数ではないと仮定するべきですか?

1013
MSalters

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

t * f;

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

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

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

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

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

これは機能し、実際には標準により可能な実装アプローチとして許可されています。これらのコンパイラは基本的にテンプレートのテキストを内部バッファにコピーし、インスタンス化が必要な場合にのみテンプレートを解析し、定義のエラーを検出します。しかし、テンプレートの作成者が作成したエラーでテンプレートのユーザー(同僚が悪い!).

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

「typename」キーワード

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

テンプレート宣言または定義で使用され、template-parameterに依存する名前は、該当する名前検索でタイプ名が検出されるか、キーワードtypenameで名前が修飾されない限り、タイプに名前を付けないと想定されます。

typenameが不要な多くの名前があります。コンパイラは、テンプレート定義の適切な名前検索で、コンストラクト自体の解析方法を見つけることができるためです(たとえば、Tが型テンプレートパラメータの場合、T *f;を使用)。ただし、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::functionおよびfの任意の定義を想像してください。

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

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

名前ルックアップ(3.4)で名前がテンプレート名であることが検出された後、この名前の後に<が続く場合、<は常にtemplate-argument-listの先頭と見なされ、名前の後にless-より演算子。

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

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

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

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

依存関係

棚に厚いスタンダードの本があり、私が何について話していたかを正確に知りたい人のために、これがスタンダードでどのように指定されているかについて少しお話します。

テンプレート宣言では、テンプレートをインスタンス化するために使用するテンプレート引数に応じて、一部の構造体の意味が異なります。式には異なるタイプまたは値があり、変数には異なるタイプがあり、関数呼び出しでは異なる関数が呼び出される場合があります。このような構成体は、一般的に、テンプレートパラメーターではdependと言われます。

規格は、構造が依存しているかどうかによってルールを正確に定義します。それらを論理的に異なるグループに分けます。1つは型をキャッチし、もう1つは式をキャッチします。式は、値やタイプによって異なります。そこで、典型的な例を追加しました:

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

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

従属名

標準は、exactly依存名であるかどうかについて少し不明瞭です。単純な読み取り(ご存じのとおり、最も驚きの少ない原則)では、従属名として定義されているのは、以下の関数名の特殊なケースです。しかし、明らかにT::xもインスタンス化コンテキストで検索する必要があるため、従属名である必要もあります(残念なことに、C++ 14半ばの時点で、委員会はこの混乱する定義を修正する方法を検討し始めました)。

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

名前は、エンティティまたはラベルを示す識別子(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>です。これらはすべて名前であり、標準の従来の使用では、名前には、名前を検索する名前空間またはクラスを示す修飾子を含めることもできます。

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

従属関数名

この記事の主な関心事ではありませんが、言及する価値があります。関数名は例外として扱われ、個別に処理されます。識別子関数名は、それ自体ではなく、呼び出しで使用される型依存の引数式に依存します。 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宣言では、最後の::の後にtemplateを使用することはできません。また、C++委員会 said はソリューションに取り組みません。

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

C++ 11

問題

typenameおよびtemplateが必要な場合のC++ 03のルールは大部分が合理的ですが、その定式化には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_typeintにしかならず(したがって型である)、this->gにしかなれないことをコンパイラが完全に把握できたとしても、曖昧性除去キーワードが必要です。メンバーテンプレートgは後で宣言されます(Aがどこかで明示的に特化されていても、そのテンプレート内のコードには影響しないので、Aを後で特化しても意味は影響を受けません。 )。

現在のインスタンス化

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

この概念に基づいて、言語では、CurrentInstantiation::FooFoo、およびCurrentInstantiationTyped->FooA *a = this; a->Fooなど)はすべて現在のインスタンス化のメンバーifそれらは、現在のインスタンス化またはその非依存基本クラスの1つであるクラスのメンバーであることがわかります(名前検索をすぐに実行することにより) 。

修飾子が現在のインスタンス化のメンバーである場合、キーワードtypenameおよびtemplateは不要になりました。ここで覚えておくべきキーポイントは、A<T>stillタイプ依存の名前であることです(すべての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;        
  }
};

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

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

D<int>::fをインスタンス化するときにエラーをキャッチするには、コンパイラーが必要です。したがって、2つの世界のベストを取得できます。依存ベースクラスで問題が発生した場合に保護する「遅延」ルックアップ、およびtypenameおよびtemplateから解放される「即時」ルックアップです。

不明な専門分野

Dのコードでは、名前typename D::questionable_typeは現在のインスタンス化のメンバーではありません。代わりに、言語は不明な専門分野のメンバーとしてマークします。特に、これはDependentTypeName::FooまたはDependentTypedName->Fooを実行していて、依存型がnotである場合に常に当てはまります(この場合、コンパイラーはあきらめ、 「後でFooが何であるかを調べる)またはit isと言うと、現在のインスタンス化とその名前がそのインスタンスまたはその非依存ベースクラスで見つからず、依存ベースクラスもあります。

上記で定義したhクラステンプレート内にメンバー関数Aがあった場合に何が起こるか想像してみてください

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

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

例と雑学

this answer でこの知識を試すことができ、実際の例で上記の定義が意味をなすかどうかを確認できます(それらの答えは、あまり詳細ではありません)。

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が検索されます。

131
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 };
};

シモンズ: ブースト::バリアント を見てください

PS2: タイプリスト 、特にAndrei Alexandrescuの本、Modern C++ Designを見てください。

20
Luc Touraille

この答えはタイトル付きの質問(の一部)に答えるためのやや短くて甘いものであることを意味しています。あなたがそれらをそこに置かなければならない理由を説明するより詳細な答えが欲しいならば、ここに行きなさい。


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ではなく正しくこれを解析します。

19
Rapptz

私はJLBorgesの優れた 返答 をcplusplus.comからの逐語的な質問に置きます。というのも、これは私がこの主題について読んだ最も簡潔な説明だからです。

私たちが作成したテンプレートには、使用できる名前が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は、型を参照し、テンプレートパラメータに依存する修飾名がある場合に限り、テンプレートの宣言と定義でのみ使用してください。

2
Nikos