Mylący błąd szablonu

91

Bawiłem się przez jakiś czas z clang i natknąłem się na "test / SemaTemplate / dependent-template-recovery.cpp" (w dystrybucji clang), który ma dostarczać wskazówek, jak odzyskać dane po błędzie szablonu.

Całość można łatwo sprowadzić do minimalnego przykładu:

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

Komunikat o błędzie wygenerowany przez clang:

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

... Ale trudno mi zrozumieć, gdzie dokładnie należy wstawić templatesłowo kluczowe, aby kod był poprawny składniowo?

Prasoon Saurav
źródło
11
Czy próbowałeś wstawić go tam, gdzie wskazuje strzałka?
Mike Seymour,
3
Podobnie jak to i to
Prasoon Saurav

Odpowiedzi:

104

ISO C ++ 03 14.2 / 4:

Gdy nazwa specjalizacji szablonu członka pojawia się po. lub -> w wyrażeniu-przyrostku lub po specyfikatorze-nazwy-zagnieżdżonej-w-kwalifikowanym-id, a wyrażenie-przyrostka-kwalifikowanego-identyfikatora wyraźnie zależy od parametru-szablonu (14.6.2), nazwa szablonu elementu musi być poprzedzony szablonem słów kluczowych . W przeciwnym razie zakłada się, że nazwa określa nazwę niebędącą szablonem.

In t->f0<U>(); f0<U>to specjalizacja szablonu elementu członkowskiego, która pojawia się po ->i która wyraźnie zależy od parametru szablonu U, więc specjalizacja szablonu elementu członkowskiego musi być poprzedzona templatesłowem kluczowym.

Więc zmień t->f0<U>()na t->template f0<U>().

Prasoon Saurav
źródło
Co ciekawe, pomyślałem, że umieszczenie wyrażenia w nawiasach t->(f0<U>())naprawiłoby to, tak jak myślałem, że to f0<U>()
24
Czy mógłbyś skomentować, dlaczego tak się dzieje? Dlaczego C ++ miałby wymagać tego rodzaju składni?
Ciekawy
2
Tak, to jest dziwne. Język może „wykryć”, że szablonowe słowo kluczowe musi być obecne. Jeśli może to zrobić, powinien po prostu „wstawić” słowo kluczowe do siebie.
Enrico Borba
26

Oprócz uwag innych, zwróć uwagę, że czasami kompilator nie mógł się zdecydować i obie interpretacje mogą przy tworzeniu instancji dostarczyć alternatywne prawidłowe programy

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

Wydrukuje się, 0gdy pomijamy templatewcześniej, f<int()>ale 1podczas wstawiania. Zostawiam to jako ćwiczenie, aby dowiedzieć się, co robi kod.

Johannes Schaub - litb
źródło
3
To diabelski przykład!
Matthieu M.
1
Nie mogę odtworzyć zachowania, które opisujesz w Visual Studio 2013. Zawsze wywołuje f<U>i zawsze drukuje 1, co dla mnie ma sens. Nadal nie rozumiem, dlaczego templatesłowo kluczowe jest wymagane i jakie ma znaczenie.
Violet Giraffe
@Violet, kompilator VSC ++ nie jest zgodnym kompilatorem C ++. Potrzebne jest nowe pytanie, jeśli chcesz wiedzieć, dlaczego VSC ++ zawsze drukuje 1.
Johannes Schaub - litb
1
Ta odpowiedź wyjaśnia, dlaczego templatejest potrzebna: stackoverflow.com/questions/610245/ ... bez polegania wyłącznie na standardowych terminach, które są trudne do zrozumienia. Proszę zgłosić, jeśli cokolwiek w tej odpowiedzi jest nadal niejasne.
Johannes Schaub - litb
@ JohannesSchaub-litb: Dzięki, świetna odpowiedź. Okazuje się, że czytałem to już wcześniej, ponieważ zostało już przeze mnie przegłosowane. Najwyraźniej moja pamięć jest meh.
Violet Giraffe
12

Wstaw go tuż przed punktem, w którym znajduje się daszek:

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

Edycja: przyczyna tej reguły staje się jaśniejsza, jeśli myślisz jak kompilator. Kompilatory generalnie patrzą w przód tylko na jeden lub dwa tokeny na raz i generalnie nie „patrzą w przyszłość” na resztę wyrażenia. [Edytuj: patrz komentarz] Powód słowa kluczowego jest taki sam, jak dlaczego potrzebujesz typenamesłowa kluczowego do wskazywania nazw typów zależnych: mówi kompilatorowi „hej, identyfikator, który zobaczysz, to nazwa szablonu, a nie nazwa statycznego elementu danych, po którym następuje znak mniejszości ".

Doug
źródło
1
Nigdy bym się tego nie domyślił ... ale dziękuję ;-). zawsze jest coś do nauczenia się o C ++!
3
Nawet z nieskończonym wyprzedzeniem nadal będziesz potrzebować template. Istnieją przypadki, w których zarówno z, jak i bez template, dają prawidłowe programy o różnym zachowaniu. Jest to więc nie tylko problem syntaktyczny ( t->f0<int()>(0)jest poprawny składniowo zarówno dla wersji less-than, jak i listy argumentów szablonu).
Johannes Schaub - litb
@Johannes Schaub - litb: Tak, więc jest to bardziej problem przypisania spójnego znaczenia semantycznego wyrażeniu niż patrzenia w przyszłość.
Doug
11

Fragment z szablonów C ++

Konstrukcja .template Bardzo podobny problem został wykryty po wprowadzeniu typename. Rozważmy następujący przykład przy użyciu standardowego typu zestawu bitów:

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

Dziwną konstrukcją w tym przykładzie jest .template. Bez tego dodatkowego użycia szablonu kompilator nie wie, że następujący po nim token mniejszy niż (<) nie jest tak naprawdę „mniejszym niż”, ale początkiem listy argumentów szablonu. Zauważ, że jest to problem tylko wtedy, gdy konstrukcja przed okresem zależy od parametru szablonu. W naszym przykładzie parametr bs zależy od parametru szablonu N.

Podsumowując, notacja .template (i podobne notacje, takie jak -> szablon) powinny być używane tylko wewnątrz szablonów i tylko wtedy, gdy są zgodne z czymś, co zależy od parametru szablonu.

Chubsdad
źródło