Vorlagen-Disambiguator für abhängige Namen

8

Beim Kompilieren des folgenden Beispiels ist das folgende Problem aufgetreten:

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

Link zum Compiler Explorer: https://godbolt.org/z/js4XaP

Der Code wird mit GCC 9.2 und MSVC 19.22 kompiliert, jedoch nicht mit clang 9.0.0. Clang gibt an, dass ein Vorlagenschlüsselwort erforderlich ist. Wenn static constexpr int RayDim = 3;in int func() constClang bewegt wird, akzeptiert es.

Ist das Template-Schlüsselwort, wie im Codeblock als Kommentar angegeben, erforderlich für yF.head<1>()?

Jodebo
quelle
Ich denke, Sie könnten auf ein clang-spezifisches Implementierungsproblem stoßen,
Eddy Luten
Ich denke, es Matrix<RayDim>ist kein abhängiger Typ und daher ist das Schlüsselwort nicht erforderlich. Ich kann später Zeit für eine Antwort haben.
Aschepler
Bei @EddyLuten P0522 geht es um Vorlagenvorlagenparameter, und in diesem Beispiel gibt es keine.
Aschepler
Ein vereinfachtes Beispiel, das verwandt zu sein scheint: godbolt.org/z/KpXpYs
Evg

Antworten:

1

Das templateSchlüsselwort sollte hier nicht erforderlich sein, daher ist clang falsch, um das Programm abzulehnen.

Alle C ++ Standard-Abschnitts- und Absatznummern und Anführungszeichen unten sind für den C ++ 17-Entwurf N4659 und den aktuell verknüpften C ++ 20-Entwurf identisch.

Die Anforderung für templatenach einem .oder ->oder ::Token beim Benennen einer Mitgliedsvorlage befindet sich in [temp.names] / 4 . Der Absatz listet zuerst Fälle auf, in denen das Schlüsselwort nicht zulässig ist, dann Fälle, in denen es optional ist und keinen Unterschied macht. Dann:

In allen anderen Kontexten wird beim Benennen einer Vorlagenspezialisierung eines Mitglieds einer unbekannten Spezialisierung ( [temp.dep.type] ) dem Namen der Mitgliedsvorlage das Schlüsselwort vorangestellt template.

Ein "Mitglied einer unbekannten Spezialisierung" ist ein Mitglied eines abhängigen Typs, der nicht "die aktuelle Instanziierung" ist. Die Frage ist also, ob Matrix<RayDim>es sich um einen abhängigen Typ handelt. Dazu schauen wir uns [temp.dep.type] / 9 an:

Ein Typ ist abhängig, wenn dies der Fall ist

  • ein Vorlagenparameter,
  • ein Mitglied einer unbekannten Spezialisierung,
  • eine verschachtelte Klasse oder Aufzählung, die ein abhängiges Mitglied der aktuellen Instanziierung ist,
  • ein cv-qualifizierter Typ, bei dem der cv-nicht qualifizierte Typ abhängig ist,
  • ein zusammengesetzter Typ, der aus einem abhängigen Typ aufgebaut ist,
  • ein Array-Typ, dessen Elementtyp abhängig ist oder dessen Bindung (falls vorhanden) wertabhängig ist,
  • ein Funktionstyp, dessen Ausnahmespezifikation wertabhängig ist,
  • Eine einfache Vorlagen-ID, bei der entweder der Vorlagenname ein Vorlagenparameter oder eines der Vorlagenargumente ein abhängiger Typ oder ein Ausdruck ist, der typ- oder wertabhängig ist oder eine Pack-Erweiterung darstellt, oder
  • bezeichnet durch decltype(Ausdruck) , wobei Ausdruck typabhängig ist.

Matrix<RayDim>ist eindeutig kein Vorlagenparameter, keine Art von Mitglied, cv-qualifiziert, ein Array-Typ, ein Funktionstyp oder angegeben durch decltype. Es ist ein zusammengesetzter Typ, verwendet jedoch nur einen Vorlagennamen und einen Ausdruck und wird daher nicht aus einem anderen Typ erstellt.

Damit bleibt der Fall der einfachen Vorlagen-ID . Der Vorlagenname Matrixist kein Vorlagenparameter. Das Vorlagenargument RayDimist ein Ausdruck. Überprüfen Sie nun, ob es typ- oder wertabhängig ist.

" Typabhängig " ist in [temp.dep.expr] definiert . Nur Absatz 3 kann für eine einzelne Kennung gelten wie RayDim:

Ein ID-Ausdruck ist typabhängig, wenn er enthält

  • eine Kennung, die durch Namenssuche mit einer oder mehreren Deklarationen verknüpft ist, die mit einem abhängigen Typ deklariert sind;
  • eine Kennung, die durch die Namenssuche mit einem Nicht-Typ- Vorlagenparameter verknüpft ist , der mit einem Typ deklariert ist, der einen Platzhaltertyp enthält.
  • ein Bezeichner , der durch die Namenssuche einer Variablen zugeordnet wird, die mit einem Typ deklariert ist, der einen Platzhaltertyp ([dcl.spec.auto]) enthält, wobei der Initialisierer typabhängig ist.
  • eine Kennung, die durch Namenssuche einer oder mehreren Deklarationen von Elementfunktionen der aktuellen Instanziierung zugeordnet ist, die mit einem Rückgabetyp deklariert wurden, der einen Platzhaltertyp enthält;
  • ein Bezeichner , der durch Namenssuche einer strukturierten Bindungsdeklaration zugeordnet ist, deren Klammer-oder-Gleich-Initialisierer typabhängig ist,
  • der Bezeichner __func__([dcl.fct.def.general]), wobei jede einschließende Funktion eine Vorlage, ein Mitglied einer Klassenvorlage oder ein generisches Lambda ist;
  • eine Vorlagen-ID , die abhängig ist,
  • eine Konvertierungsfunktions-ID , die einen abhängigen Typ angibt, oder
  • ein verschachtelter Namensspezifizierer oder eine qualifizierte ID , die ein Mitglied einer unbekannten Spezialisierung benennt;

oder wenn es ein abhängiges Mitglied der aktuellen Instanziierung benennt, das Tfür einige ein statisches Datenelement vom Typ "Array mit unbekannter Grenze von " ist T([temp.static]).

RayDimsicherlich enthält keine __func__, Template-id , Umwandlung-function-id , verschachtelte-name-Spezifizierer oder qualifizierte Nummer . Die Namenssuche findet die statische Elementdeklaration der Klassenvorlage. Diese Deklaration von RayDimist sicherlich kein Template-Parameter , keine Member-Funktion oder eine strukturierte Bindungsdeklaration, und ihr Typ const intist sicherlich kein abhängiger Typ oder Array-Typ und enthält keinen Platzhaltertyp. Ist RayDimalso nicht typabhängig.

" Wertabhängig " ist in [temp.dep.constexpr] definiert . Die einzigen Fälle, die für eine einzelne Kennung wie RayDimgelten können, sind in Absatz 2 aufgeführt:

Ein ID-Ausdruck ist wertabhängig, wenn:

  • es ist typabhängig,
  • Es ist der Name eines Nicht-Typ-Vorlagenparameters.
  • Sie nennt ein statisches Datenelement , das ein abhängiges Element der aktuellen Instanziierung ist und wird in einem nicht initialisierten Mitglied-declarator ,
  • Es benennt eine statische Elementfunktion, die ein abhängiges Element der aktuellen Instanziierung ist, oder
  • Es ist eine Konstante mit Literal-Typ und wird mit einem Ausdruck initialisiert, der wertabhängig ist.

Von oben RayDimist nicht typabhängig. Es ist sicherlich kein Vorlagenparameter oder eine Elementfunktion. Es ist ein statisches Datenelement und abhängiges Mitglied der aktuellen Instanziierung, aber es wird in dem initialisierten Mitglied-declarator . Das heißt, das " = 3" wird in der Klassendefinition angezeigt, nicht in einer separaten Elementdefinition. Es ist eine Konstante mit Literal-Typ, aber ihr Initialisierer 3ist nicht wertabhängig.

Ist RayDimalso nicht wert- oder typabhängig. Daher Matrix<RayDim>ist es kein abhängiger Typ, yF.headkein Mitglied einer unbekannten Instanziierung, und das templatevorhergehende Schlüsselwort headist optional und nicht erforderlich. (Dies ist zulässig, da es sich nicht um einen "Nur-Typ-Kontext" handelt und headtatsächlich eine Mitgliedsvorlage benennt.)

aschepler
quelle
0

Haftungsausschluss

Dies ist keine Antwort, sondern ein langer Kommentar

Meine Kenntnisse als sind zu gering, um den Standard vollständig zu verstehen, aber hier sind einige Dinge, die ich beim Experimentieren mit dem Code entdeckt habe. Alles, was folgt, basiert auf meinem (alles andere als perfekten) Verständnis der Angelegenheit und benötigt wahrscheinlich einige Überprüfungen.

Ermittlung

Zunächst ging ich zu einer vollständig definierten Standardversion (C ++ 17), damit wir an einer genau definierten Implementierung arbeiten.

Wenn man sich diesen Code ansieht, scheint es, dass MSVC immer noch einige Probleme hat (mit seiner Suche, denke ich?), Wenn es um die Instanziierung und Neudefinition von Vorlagen geht. Ich würde MSVC in unserem Szenario nicht so sehr vertrauen.

Lassen Sie uns darüber nachdenken, warum wir das templateSchlüsselwort bei brauchen könnten

return yF.template head<1>();

Abhängige Namen

In Vorlagen müssen wir dem Compiler manchmal bei der Entscheidung helfen, ob sich ein Name auf ihn bezieht

  1. ein Wert int T::x = 0,
  2. ein Typ struct T::x {};oder
  3. eine Vorlage template <typename U> T::foo<U>();

Wenn wir uns auf einen Wert beziehen, tun wir nichts. Wenn wir uns auf einen Typ beziehen, müssen wir verwenden typename. Und wenn wir uns auf eine Vorlage beziehen, die wir verwenden template. Mehr zu diesem Thema finden Sie hier .

Ich verstehe die Standardspezifikation nicht, wenn es um die tatsächliche Definition eines abhängigen Namens geht, aber hier sind einige Beobachtungen.

Beobachtungen

Schauen wir uns den Referenzcode an

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();
}

Normalerweise RayDimsollte es sich um einen abhängigen Namen handeln (da er sich in der Vorlage befindet Test), der auch Matrix<RayDim>zu einem abhängigen Namen führen würde. Nehmen wir zunächst an, dass es sich Matrix<RayDim>tatsächlich um einen abhängigen Namen handelt. Dies macht auch Matrix<RayDim>::headeinen abhängigen Namen. Da Matrix<RayDim>::heades sich um eine Vorlagenfunktion handelt, handelt es sich um eine Vorlage an sich, und es gelten die Regeln für abhängige Namen von oben, sodass wir das templateSchlüsselwort verwenden müssen. Darüber beschwert sich Clang.

Da es jedoch innerhalb derselben Vorlage RayDimdefiniert ist Testund funcauch innerhalb derselben Vorlage definiert ist und keine Vorlagenfunktion an sich, denke ich nicht, dass dies RayDimtatsächlich ein abhängiger Name im Kontext von ist func. Darüber hinaus RayDimstützt sich nicht auf die Vorlagenargumente von Test. In diesem Fall Matrix<RayDim>und Matrix<RayDim>::headjeweils würden, werden nicht-abhängige Namen, die uns die weglassen können templateSchlüsselwort. Aus diesem Grund kompilieren gcc (und msvc).

Wenn wir auch eine Vorlage RayDimerstellen würden, wie hier

template <typename>
static constexpr int RayDim = 3;

gcc würde es auch als abhängigen Namen behandeln (was richtig ist, da es später möglicherweise eine Vorlagenspezialisierung gibt, sodass wir es zu diesem Zeitpunkt noch nicht wissen). In der Zwischenzeit akzeptiert msvc gerne alles, was wir darauf werfen.

Fazit

Es scheint, als würde es darauf ankommen, ob RayDimes sich um einen abhängigen Namen im Kontext von handelt Test<T>::funcoder nicht. Clang glaubt es ist, gcc nicht. Von einigen weiteren Tests sieht es aus wie MSVC-Seiten mit Klirren auf diesem. Aber es macht auch irgendwie sein eigenes Ding, also wer weiß?

Ich würde mich hier auf die Seite von gcc stellen, da ich keine Möglichkeit sehe RayDim, an dem Punkt, an dem funcinstanziiert wird , abhängig zu werden .

Timo
quelle