従属名のテンプレートの明確化


8

次の例をコンパイルして、次の問題が発生しました。

template <int N>
class Matrix {
public:
    template <int Idx>
    int head() {
        return Idx;
    }
};

template <typename T>
class Test {
    static constexpr int RayDim = 3;
public:
    int func() const {
        Matrix<RayDim> yF;
        return yF.head<1>();
        //        ^ is template keyword required here?
    }
};

struct Empty {};

void test() {
    Test<Empty> t;
}

コンパイラエクスプローラーへのリンク:https : //godbolt.org/z/js4XaP

コードはGCC 9.2およびMSVC 19.22でコンパイルされますが、clang 9.0.0ではコンパイルされません。Clangは、テンプレートキーワードが必要であると述べています。clangにstatic constexpr int RayDim = 3;移動した場合はint func() constそれを受け入れます。

コードブロックのコメントとして述べたように、テンプレートキーワードは必要ですyF.head<1>()か?


テンプレートタイプとパラメータのマッチングがまだ完全に実装されていない、clang固有の実装の問題が発生している可能性があります。clang.llvm.org
Eddy

私が思うにMatrix<RayDim>依存型ではありませんので、キーワードが必要とされていません。後で答える時間があるかもしれません。
aschepler

@EddyLuten P0522はテンプレートテンプレートパラメータに関するものであり、この例には何もありません。
aschepler

関連していると思われる簡単な例:godbolt.org/z/KpXpYs
Evg

回答:


1

ここではtemplateキーワードは必須ではないため、clangはプログラムを拒否するために正しくありません。

以下のすべてのC ++標準セクションおよび段落番号と引用は、C ++ 17ドラフトN4659と現在リンクされているC ++ 20ドラフトと同じです。

メンバーテンプレートに名前を付ける場合のor またはトークンのtemplate後の要件は[temp.names] / 4です。この段落では、最初にキーワードが許可されない場合をリストし、次にそれがオプションであり、違いがない場合をリストしています。.->::

他のすべてのコンテキストでは、不明な特殊化([temp.dep.type])のメンバーのテンプレート特殊化に名前を付ける場合、メンバーテンプレート名の前にキーワードを付けtemplateます。

「不明な特殊化のメンバー」は、「現在のインスタンス化」ではない依存型のメンバーです。したがって、問題はMatrix<RayDim>依存型かどうかです。そのために、[temp.dep.type] / 9を調べます。

ある場合、タイプは依存しています

  • テンプレートパラメータ
  • 不明な専門分野のメンバー、
  • 現在のインスタンス化の依存メンバーであるネストされたクラスまたは列挙、
  • cv非修飾型が依存するcv修飾型、
  • 依存型から構成される複合型
  • 要素の型が依存する、またはその境界(存在する場合)が値に依存する配列型
  • 例外指定が値に依存する関数型、
  • シンプル・テンプレートID、テンプレート名のいずれかが、テンプレートパラメータであるか、またはテンプレート引数のいずれかが依存型または型依存性または値に依存するか、またはパック拡張である式である、又は
  • decltype()で示されますはタイプに依存します。

Matrix<RayDim>明らかに、テンプレートパラメータ、任意の種類のメンバー、cv修飾、配列型、関数型、またはで指定されたものではありませんdecltype。これは複合型ですが、テンプレート名と式のみを使用するため、他の型からは作成されません。

その葉の単純なテンプレート-IDのケース。テンプレート名Matrixはテンプレートパラメータではありません。テンプレート引数RayDimは式なので、次に、型に依存するか値に依存するかを確認します。

「タイプ依存」は[temp.dep.expr]で定義されています。段落3のみが次のような単一の識別子に適用できますRayDim

ID-expressionはそれが含まれている場合のタイプに依存します

  • 依存型で宣言された1つまたは複数の宣言と名前のルックアップによって関連付けられた識別子
  • プレースホルダタイプを含むタイプで宣言された非タイプテンプレートパラメータを使用した名前ルックアップによって関連付けられた識別子
  • イニシャライザがタイプに依存するプレースホルダタイプ([dcl.spec.auto])を含むタイプで宣言された変数と、名前のルックアップによって関連付けられた識別子
  • プレースホルダー型を含む戻り型で宣言された現在のインスタンス化のメンバー関数の1つ以上の宣言に名前の検索によって関連付けられた識別子
  • 名前検索によって、ブレースまたはイコライザーが型に依存する構造化バインディング宣言に関連付けられた識別子
  • 識別子__func__([dcl.fct.def.general])。ここで、すべての囲み関数は、テンプレート、クラステンプレートのメンバー、またはジェネリックラムダです。
  • 依存するテンプレートID
  • 依存型を指定するconversion-function-id、または
  • ネストされた名指定子または修飾-idの名前、未知の専門のメンバーという。

または、現在のインスタンス化の従属メンバーである名前がT「不明な境界の配列」の静的データメンバーである場合T([temp.static])。

RayDimもちろん__func__template-idconversion-function-idnested-name-specifier、またはqualified-idは含まれていません。名前ルックアップは、クラステンプレートの静的メンバー宣言を見つけます。その宣言RayDimは確かにtemplate-parameter、メンバー関数、または構造化バインディング宣言でconst intはなく、その型は依存型または配列型ではなく、プレースホルダー型を含みません。したがってRayDim、型に依存しません。

「値依存」は[temp.dep.constexpr]で定義されています。のような単独の識別子に適用できるのRayDimは、段落2だけです。

ID-expressionがあれば値に依存します:

  • タイプに依存します
  • タイプではないテンプレートパラメータの名前です。
  • 現在のインスタンス化の依存メンバーであり、member-declaratorで初期化されていない静的データメンバーを指定します。
  • 現在のインスタンス化の依存メンバーである静的メンバー関数を指定する、または
  • リテラル型の定数であり、値に依存する式で初期化されます。

上から、RayDim型に依存しません。これは確かにテンプレートパラメータでもメンバー関数でもありません。これは静的データメンバーであり、現在のインスタンス化の依存メンバーですが、member-declaratorで初期化されます。つまり、「= 3」は個別のメンバー定義ではなく、クラス定義内に表示されます。リテラル型の定数ですが、初期化子3は値に依存しません。

したがってRayDim、値やタイプに依存しません。したがってMatrix<RayDim>yF.headは依存型ではなく、不明なインスタンス化のメンバーではなく、templatebeforeキーワードheadはオプションであり、必須ではありません。(「タイプのみのコンテキスト」ではなくhead、実際にメンバーテンプレートに名前を付けるため、許可されます。)


0

免責事項

これは答えではなく、長いコメントです

私のスキルは標準を完全に理解するには低すぎますが、ここでは、コードを実験して発見したいくつかのことを示します。以下のすべては、私が(完全とはほど遠い)問題についての理解に基づいており、おそらくいくつかのレビューが必要です。

調査

まず、完全に定義された標準バージョン(C ++ 17)に行ったので、明確に定義された実装で動作します。

このコードを見ると、テンプレートのインスタンス化と再定義に関しては、MSVCにはまだいくつかの問題があるようです(そのルックアップに関してはそうでしょうか?)。このシナリオでは、MSVCをそれほど信頼しません。

ということで、なぜtemplateキーワードが必要なのかを考えてみましょう

return yF.template head<1>();

従属名

時々、テンプレート内で、名前が参照するかどうかをコンパイラが決定するのを助ける必要があります

  1. int T::x = 0
  2. タイプstruct T::x {};、または
  3. テンプレート template <typename U> T::foo<U>();

値を参照する場合、何も行いません。タイプを参照する場合は、を使用する必要がありますtypename。また、テンプレートを参照する場合は、を使用しますtemplate。そのテーマの詳細については、こちらをご覧ください

従属名の実際の定義に関しては、標準仕様を理解していませんが、ここにいくつかの観察があります。

観察

参照コードを見てみましょう

template <int N>
struct Matrix 
{
    template <int Idx>
    int head() { return Idx; }
};


template <typename T>
struct Test 
{    
    static constexpr int RayDim = 3;

    int func() const 
    {
        Matrix<RayDim> yF;
        return yF.head<1>(); // clang complains, gcc and msvc are ok
    }
};


struct Empty {};

int test() 
{
    Test<Empty> t;
    return t.func();
}

通常RayDimは従属名である必要があります(テンプレート内にあるためTest)。これMatrix<RayDim>も従属名になります。とりあえず、それがMatrix<RayDim>実際に依存名であると仮定しましょう。これによりMatrix<RayDim>::head、依存する名前も作成されます。以来Matrix<RayDim>::headテンプレート機能があり、それ自体がテンプレートと使用するために私たちを必要とする適用上から依存名の規則、あるtemplateキーワードが。これはclangが不満を言っていることです。

ただし、RayDimは内Testfunc定義され、同じテンプレート内でも定義されており、テンプレート化された関数自体ではないため、RayDim実際にはのコンテキストでの依存名ではないと思いますfunc。さらに、RayDimのテンプレート引数に依存しませんTest。この場合、Matrix<RayDim>およびMatrix<RayDim>::headそれぞれ、私たちは省略することを可能にする、非依存の名前になるtemplateキーワードを。これが、gcc(およびmsvc)がコンパイルされる理由です。

ここでもテンプレートを作成RayDimするとします

template <typename>
static constexpr int RayDim = 3;

gccもそれを依存名として扱います(これは正しいです。後でテンプレートの特殊化が行われる可能性があるため、その時点ではわかりません)。一方、msvcは、投げたすべてのものを喜んで受け入れます。

結論

RayDim依存関係の名前がであるかどうかで沸騰しているようTest<T>::funcです。Clangはそうだと考えていますが、gccはそうではありません。いくつかのより多くのテストから、これはclangがこの1つにあるmsvcサイドのように見えます。しかし、それはまた、それが独自のことをしているので、誰が知っていますか?

がインスタンス化RayDimされる時点で依存する可能性のある方法がないので、ここではgccを使用しfuncます。

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