web-dev-qa-db-ja.com

VSではなくg ++でtypedef typenameを使用する必要があるのはなぜですか?

GCCがこの問題に気付いてからしばらく経ちましたが、それは今日起こったばかりです。しかし、なぜGCCがテンプレート内でtypedef typenameを必要とするのか理解できませんでしたが、VSとICCはそうではないと思います。 typedef typenameは「バグ」または過大な標準ですか、それともコンパイラの作成者に任されているものですか?

ここで私が何を言っているのかわからない人のためのサンプルがあります:

template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
    std::map<KEY,VALUE>::const_iterator iter = container.find(key);
    return iter!=container.end();
}

上記のコードはVS(おそらくICC)でコンパイルされますが、次のようにしたいため、GCCでは失敗します。

template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
    typedef typename std::map<KEY,VALUE>::const_iterator iterator; //typedef typename
    iterator iter = container.find(key);
    return iter!=container.end();
}

注:これは私が使用している実際の関数ではなく、問題を示す愚かなものです。

50
Robert Gould

タイプ名は標準で必須です。テンプレートのコンパイルには2段階の検証が必要です。最初のパスでは、コンパイラーは型の置換を実際に提供せずにテンプレート構文を検証する必要があります。このステップでは、std :: map :: iteratorが値であると想定されています。タイプを表す場合は、typenameキーワードが必要です。

なぜこれが必要なのですか?実際のKEY型とVALUE型を置き換える前に、コンパイラーはテンプレートが特殊化されておらず、特殊化がiteratorキーワードを別のものとして再定義していないことを保証できません。

次のコードで確認できます。

class X {};
template <typename T>
struct Test
{
   typedef T value;
};
template <>
struct Test<X>
{
   static int value;
};
int Test<X>::value = 0;
template <typename T>
void f( T const & )
{
   Test<T>::value; // during first pass, Test<T>::value is interpreted as a value
}
int main()
{
  f( 5 );  // compilation error
  X x; f( x ); // compiles fine f: Test<T>::value is an integer
}

最後の呼び出しは失敗し、最初のテンプレートコンパイルステップ中にf() Test :: valueが値として解釈されたが、タイプがXのTest <>テンプレートのインスタンス化により、タイプ。

まあ、GCCは実際にはrequiretypedef-typenameで十分ではありません。これは機能します:

#include <iostream>
#include <map>

template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
    typename std::map<KEY,VALUE>::const_iterator iter = container.find(key);
    return iter!=container.end();
}

int main() {
    std::map<int, int> m;
    m[5] = 10;
    std::cout << find(m, 5) << std::endl;
    std::cout << find(m, 6) << std::endl;
    return 0;
}

これは、状況依存の解析問題の例です。問題の行が何を意味するかは、この関数の構文だけでは明らかではありません-std::map<KEY,VALUE>::const_iteratorは型かどうか。

今、私は何の例を考えることができないようです...::const_iteratorはタイプ以外の可能性があり、これもエラーではありません。したがって、コンパイラはそれが型であることをhasであると知ることができると思いますが、貧弱なコンパイラ(ライター)にとっては難しいかもしれません。

規格のセクション14.6/3によるlitbによると、規格ではここでtypenameを使用する必要があります。

32
Magnus Hoff

VS/ICCはどこにでもtypenameキーワードを提供しているようです考える必須です。これは悪いこと(TM)であることに注意してください-コンパイラにあなたが何を望むかを決定させます。これは、必要に応じてtypenameをスキップするという悪い癖をつけることで問題をさらに複雑にし、移植性の悪夢になります。これは間違いなく標準的な動作ではありません。厳密な標準モードまたはコモーで試してください。

4
dirkgently

これは、Microsoft C++コンパイラのバグです。例では、std :: map :: iteratorがタイプではない可能性があります(KEY、VALUEに特殊化されたstd :: mapがあり、std :: map :: iteratorが変数など)。

GCCは正しいコードを書くように強制しますが(意味が明らかであっても)、Microsoftコンパイラは正しい意味を推測します(書き込んだコードが間違っていても)。

3
JoeG

値/タイプの種類の問題は根本的な問題ではないことに注意してください。主な問題は解析です。検討する

template<class T>
void f() { (T::x)(1); }

Typenameキーワードが必須でない限り、これがキャストであるか関数呼び出しであるかを判別する方法はありません。その場合、上記のコードには関数呼び出しが含まれます。一般に、完全な解析を省略せずに選択を遅らせることはできません。フラグメントを検討してください。

(a)(b)(c)

覚えていなかったかもしれませんが、Cの関数呼び出しよりもキャストの方が優先されます。これは、Bjarneが関数スタイルのキャストを必要とした理由の1つです。したがって、上記の意味かどうかを判断することはできません

(a)(b)  (c)   // a is a typename

または

(a) (b)(c)    // a is not a typename , b is

または

(a)(b) (c)    // neither a nor b is a typename

グループ化を示すためにスペースを挿入しました。

「typename」と同じ理由で「templatename」キーワードが必要であることに注意してください。C/ C++でその種類を知らないと、構文解析できません。

2
Yttrill