正式には、typenameは何のためのものですか?


131

gccテンプレートを使用しているときに、本当に判読できないエラーメッセージが吐き出されるのを見たことがあります。具体的には、一見正しい宣言が非常に奇妙なコンパイルエラーを引き起こし、魔法のようにtypenameキーワードの先頭にプレフィックスを付けることで消えてしまうという問題がありました。宣言...(たとえば、先週、2つのイテレータを別のテンプレートクラスのメンバーとして宣言していたので、これを行わなければなりませんでした)...

何の話typename


回答:


207

以下はJosuttisの本からの引用です:

このキーワードtypenameは、後に続く識別子がタイプであることを指定するために導入されました。次の例を検討してください。

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

ここでtypenameは、SubTypeがのタイプであることを明確にするために使用され class Tます。したがって、 ptrは型へのポインタ T::SubTypeです。なしtypenameSubType は、静的メンバーと見なされます。したがって

T::SubType * ptr

値の乗算になる SubTypeタイプのTptr


2
素晴らしい本。一度お読みいただき、必要に応じて参照用として保管してください。
deft_code

1
賢明な読者は、乗算式がメンバー宣言の文法では許可されていないことに気付くでしょう。そのため、C ++ 20 はこれの必要性省きますtypename(すべてではありませんが!)。
Davis Herring

私を納得させなかった。テンプレートがインスタンス化されると、T :: Subtypeとは何かが非常に明確になります
kovarex

36

スタン・リップマンのブログ投稿は次のことを示唆しています:-

Stroustrup は、既存のプログラムを破壊する可能性のある新しいキーワードを導入するのではなく、既存のクラスキーワード再利用して型パラメーターを指定しました。新しいキーワードが考慮されなかったわけではありません。混乱を招く可能性があるため、必要とされなかっただけです。そして、ISO-C ++標準までは、これが型パラメーターを宣言する唯一の方法でした。

したがって、基本的にStroustrupは、次の理由で標準で後で変更される新しいキーワードを導入せずにクラスキーワードを再利用しました

与えられた例として

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

言語文法がT::A *aObj;算術式として誤って解釈されるため、新しいキーワードが導入され、typename

typename T::A* a6;

後続のステートメントを宣言として扱うようにコンパイラーに指示します。

キーワードは給与計算にあったので、クラスキーワードを再利用するという最初の決定によって引き起こされた混乱を修正してませんか。

だから両方ある

あなたはこの投稿を見ることができます、それは間違いなくあなたを助けます、私はできるだけそれから抽出しました


はい。typenameでもclass、同じ目的で既存のキーワードを使用できるのに、なぜ新しいキーワードが必要だったのでしょうか。
Jesper、

5
@ジェスパー:Xenusの答えはここでは混乱していると思います。typenameJosuttisを引用することによるNaveenの回答で説明されているように、解析の問題を修正するために必要になりました。(私は挿入するとは思わないclassこの場所では、働いていると思います。)新しいキーワードはこの場合のために受理された後にのみ、それはまた、テンプレート引数の宣言では許可され(たか、その定義はある?)、というためclass常に多少ありました誤解を招く。
sbi 2009年

13

コードを検討する

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

残念ながら、コンパイラーは心霊である必要はなく、T :: sometypeが型名またはTの静的メンバーを参照することになるかどうかもわかりませんtypename

template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .

6

いわゆる依存型(「テンプレートパラメーターに依存」を意味する)のメンバーを参照する状況では、コンパイラーは、結果の構成の意味論的意味を常に明確に推測できるわけではありません。 (つまり、型の名前、データメンバーの名前、または何か別の名前かどうか)。そのような場合は、その名前がその依存型のメンバーとして定義されているタイプ名に属していることをコンパイラーに明示的に伝えて、状況を明確にする必要があります。

例えば

template <class T> struct S {
  typename T::type i;
};

この例ではtypename、コードをコンパイルするために必要なキーワードです。

依存型のテンプレートメンバー、つまりテンプレートを指定する名前を参照する場合も同じことが起こります。キーワードを使用してコンパイラを支援する必要もありますが、template配置は異なります

template <class T> struct S {
  T::template ptr<int> p;
};

場合によっては、両方を使用する必要があります

template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(私が構文を正しく取得した場合)。

もちろん、キーワードの別の役割は、typenameテンプレートパラメータ宣言で使用されます。


詳細(バックグラウンド)情報については、C ++ typenameキーワードの説明も参照してください。
Atafar

5

その秘密は、テンプレートが特定のタイプに特化できるという事実にあります。つまり、いくつかのタイプで完全に異なるインターフェースを定義することもできます。たとえば、次のように書くことができます。

template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization 
struct test<int> { // for the case T is int
    T* ptr;
};

なぜこれが実際に有用であるのかを尋ねる人がいるかもしれません。それは本当に役に立たないようです。ただし、たとえばstd::vector<bool>reference型が他Tのと完全に異なるように見えることに注意してください。確かに、それは種類を別の種類に変更しませんreferenceが、それでも発生する可能性があります。

次に、このtestテンプレートを使用して独自のテンプレートを作成するとどうなりますか。このようなもの

template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

あなたそれtest<T>::ptrがタイプであることを期待しているのでそれはあなたにとって大丈夫であるようです。しかし、コンパイラはそれを知りませんし、実際には彼は標準では反対を期待するようにアドバイスさえされておりtest<T>::ptr、タイプではありません。コンパイラーに、何を追加する必要があるかを予測するには、typename前に追加する必要があります。正しいテンプレートは次のようになります

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

結論:typenameテンプレートでネストされたタイプのテンプレートを使用する場合は、常に追加する必要があります。(もちろん、テンプレートのテンプレートパラメータがその内部テンプレートに使用されている場合のみ。)


5

2つの用途:

  1. template引数キーワードとして(の代わりにclass
  2. typenameキーワードは識別子のタイプ(よりむしろ静的メンバ変数)であることをコンパイラに指示します
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}

4

すべての答えは、 typenameキーワードは2つの異なるケースで使用されます。

a)テンプレート型パラメーターを宣言するとき。例えば

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

それらの間に違いはなく、それらはまったく同じです。

b)テンプレートにネストされた依存型名を使用する前。

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

使わない typename、解析/コンパイルエラーが発生します。

Scot Meyersの本「Effective C ++」で述べられているように、2番目のケースに追加したいのは、ネストされた依存型名のtypename前に使用する例外があることです。例外は、ネストされた依存型名を基本クラスとして、またはメンバー初期化リストで使用する場合は、そこで使用しないでください。typename

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

注:typename C ++ 20以降、2番目のケース(つまり、ネストされた依存型名の前)に使用する必要はありません。


2
#include <iostream>

class A {
public:
    typedef int my_t;
};

template <class T>
class B {
public:
    // T::my_t *ptr; // It will produce compilation error
    typename T::my_t *ptr; // It will output 5
};

int main() {
    B<A> b;
    int my_int = 5;
    b.ptr = &my_int;
    std::cout << *b.ptr;
    std::cin.ignore();
    return 0;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.