Verwirrender Vorlagenfehler

87

Ich habe eine Weile mit Clang gespielt und bin auf "test / SemaTemplate / Dependent-Template-Recovery.cpp" (in der Clang-Distribution) gestoßen, das Hinweise zur Wiederherstellung nach einem Vorlagenfehler geben soll.

Das Ganze lässt sich leicht auf ein minimales Beispiel reduzieren:

template<typename T, typename U, int N> struct X {
    void f(T* t)
    {
        // expected-error{{use 'template' keyword to treat 'f0' as a dependent template name}}
        t->f0<U>();
    }
};

Die Fehlermeldung von clang:

tpl.cpp:6:13: error: use 'template' keyword to treat 'f0' as a dependent template name
         t->f0<U>();
            ^
            template 
1 error generated.

... Aber es fällt mir schwer zu verstehen, wo genau man das templateSchlüsselwort einfügen soll , damit der Code syntaktisch korrekt ist?

Prasoon Saurav
quelle
11
Haben Sie versucht, es dort einzufügen, wo der Pfeil zeigt?
Mike Seymour
3
Ähnlich wie dies und das
Prasoon Saurav

Antworten:

100

ISO C ++ 03 14.2 / 4:

Wenn der Name einer Mitgliedervorlagenspezialisierung nach angezeigt wird. oder -> in einem Postfix-Ausdruck oder nach einem verschachtelten Namensbezeichner in einer qualifizierten ID, und der Postfix-Ausdruck oder die qualifizierte ID hängt explizit von einem Vorlagenparameter (14.6.2) ab, muss der Name der Mitgliedsvorlage sein vorangestellt von der Keyword-Vorlage . Andernfalls wird angenommen, dass der Name eine Nichtvorlage benennt.

In t->f0<U>(); f0<U>ist eine Mitgliedervorlagenspezialisierung, die nach angezeigt wird ->und explizit vom Vorlagenparameter abhängt. UDaher muss der Mitgliedervorlagenspezialisierung das templateSchlüsselwort vorangestellt werden .

Also wechseln Sie t->f0<U>()zu t->template f0<U>().

Prasoon Saurav
quelle
Interessanterweise dachte ich, den Ausdruck in Klammern zu setzen: t->(f0<U>())hätte das behoben, da ich dachte, dass dies f0<U>()in einen eigenständigen Ausdruck umgewandelt werden würde ... nun, ich dachte falsch, es scheint ...
20
Könnten Sie vielleicht kommentieren, warum dies der Fall ist? Warum sollte C ++ diese Art von Syntax erfordern?
Neugierig
1
Ja das ist komisch. Die Sprache kann "erkennen", dass das Vorlagenschlüsselwort vorhanden sein muss. Wenn es das kann, sollte es einfach das Schlüsselwort dort selbst "einfügen".
Enrico Borba
25

Beachten Sie zusätzlich zu den Punkten, die andere angesprochen haben, dass sich der Compiler manchmal nicht entscheiden konnte und beide Interpretationen beim Instanziieren zu alternativen gültigen Programmen führen können

#include <iostream>

template<typename T>
struct A {
  typedef int R();

  template<typename U>
  static U *f(int) { 
    return 0; 
  }

  static int f() { 
    return 0;
  }
};

template<typename T>
bool g() {
  A<T> a;
  return !(typename A<T>::R*)a.f<int()>(0);
}


int main() {
  std::cout << g<void>() << std::endl;
}

Dies wird gedruckt, 0wenn Sie es templatevorher weglassen, f<int()>aber 1wenn Sie es einfügen. Ich lasse es als Übung, um herauszufinden, was der Code tut.

Johannes Schaub - litb
quelle
3
Das ist ein teuflisches Beispiel!
Matthieu M.
1
Ich kann das Verhalten, das Sie in Visual Studio 2013 beschreiben, nicht reproduzieren. Es ruft f<U>immer auf und druckt immer 1, was für mich absolut sinnvoll ist. Ich verstehe immer noch nicht, warum das templateSchlüsselwort erforderlich ist und welchen Unterschied es macht.
Violette Giraffe
@Violet Der VSC ++ - Compiler ist kein kompatibler C ++ - Compiler. Eine neue Frage wird benötigt, wenn Sie wissen möchten, warum VSC ++ immer 1 druckt.
Johannes Schaub - litb
1
Diese Antwort erklärt, warum dies templateerforderlich ist: stackoverflow.com/questions/610245/…, ohne sich ausschließlich auf schwer verständliche Standardbegriffe zu verlassen. Bitte melden Sie, wenn etwas in dieser Antwort noch verwirrend ist.
Johannes Schaub - litb
@ JohannesSchaub-litb: Danke, eine tolle Antwort. Es stellte sich heraus, dass ich es schon einmal gelesen habe, weil es bereits von mir positiv bewertet wurde. Anscheinend ist meine Erinnerung meh.
Violette Giraffe
12

Fügen Sie es kurz vor dem Punkt ein, an dem sich das Caret befindet:

template<typename T, typename U, int N> struct X {
     void f(T* t)
     {
        t->template f0<U>();
     }
};

Bearbeiten: Der Grund für diese Regel wird klarer, wenn Sie wie ein Compiler denken. Compiler blicken im Allgemeinen nur auf ein oder zwei Token gleichzeitig und nicht auf den Rest des Ausdrucks. [Bearbeiten: siehe Kommentar] Der Grund für das Schlüsselwort ist derselbe, warum Sie das typenameSchlüsselwort benötigen , um abhängige Typnamen anzugeben: Es sagt dem Compiler: "Hey, die Kennung, die Sie sehen werden, ist der Name einer Vorlage und nicht der Name eines statischen Datenelements, gefolgt von einem Vorzeichen ".

Doug
quelle
1
Das hätte ich nie erraten können ... aber danke ;-). Es gibt eindeutig immer etwas über C ++ zu lernen!
3
Selbst mit unendlichem Ausblick werden Sie noch brauchen template. Es gibt Fälle, in denen sowohl mit als auch ohne templategültige Programme mit unterschiedlichem Verhalten angezeigt werden. Dies ist also nicht nur ein syntaktisches Problem ( t->f0<int()>(0)gilt syntaktisch sowohl für die Version als auch für die Liste der Vorlagenargumentlisten).
Johannes Schaub - litb
@Johannes Schaub - litb: Richtig, es ist also eher ein Problem, dem Ausdruck eine konsistente semantische Bedeutung zuzuweisen, als nach vorne zu schauen.
Doug
11

Auszug aus C ++ - Vorlagen

Das .template-Konstrukt Ein sehr ähnliches Problem wurde nach der Einführung des Typnamens entdeckt. Betrachten Sie das folgende Beispiel mit dem Standard-Bitset-Typ:

template<int N> 
void printBitset (std::bitset<N> const& bs) 
{ 
    std::cout << bs.template to_string<char,char_traits<char>, 
                                       allocator<char> >(); 
} 

Das seltsame Konstrukt in diesem Beispiel ist .template. Ohne diese zusätzliche Verwendung der Vorlage weiß der Compiler nicht, dass das folgende Token (<) nicht wirklich "kleiner als" ist, sondern der Anfang einer Vorlagenargumentliste. Beachten Sie, dass dies nur dann ein Problem ist, wenn das Konstrukt vor dem Zeitraum von einem Vorlagenparameter abhängt. In unserem Beispiel hängt der Parameter bs vom Vorlagenparameter N ab.

Zusammenfassend sollte die .template-Notation (und ähnliche Notationen wie -> template) nur innerhalb von Templates verwendet werden und nur, wenn sie etwas folgen, das von einem Template-Parameter abhängt.

Chubsdad
quelle