Заплутана помилка шаблону


91

Я грався з clang деякий час, і я натрапив на "test / SemaTemplate / залежний-шаблон-відновити.cpp" (у розподілі clang), який повинен містити підказки для відновлення після помилки шаблону.

Все це можна легко звести до мінімального прикладу:

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

Повідомлення про помилку, видане clang:

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

... Але мені важко зрозуміти, куди саме слід вставити templateключове слово, щоб код був синтаксично правильним?


11
Ви намагалися вставити його туди, куди вказує стрілка?
Mike Seymour

3
Подібний до цього та цього
Prasoon Saurav

Відповіді:


104

ISO C ++ 03 14.2 / 4:

Коли ім'я спеціалізації шаблону-члена з'являється після. або -> у виразі postfix, або після вкладеного-специфікатора імені у кваліфікованому ідентифікаторі, а postfix-вираз або кваліфікований id явно залежить від параметра-шаблону (14.6.2), ім'я шаблону-члена має бути префікс шаблону ключового слова . В іншому випадку передбачається, що ім’я називає не-шаблон.

In t->f0<U>(); f0<U>- це спеціалізація шаблону-члена, яка з’являється після ->і яка явно залежить від параметра шаблону U, тому спеціалізація шаблону-члена повинна мати префікс за templateключовим словом.

Тож змініть t->f0<U>()на t->template f0<U>().


Цікаво, що я думав, що вираз викладу в дужках: t->(f0<U>())виправив би це, як я думав, що f0<U>()це

24
Чи можете ви прокоментувати, чому це так? Чому C ++ вимагає такого синтаксису?
Цікаво

2
Так, це дивно. Мова може "виявити", що ключове слово шаблону має бути присутнім. Якщо він може це зробити, тоді йому слід просто "вставити" саме ключове слово.
Енріко Борба

26

На додаток до зауважень, зазначених іншими, зауважте, що іноді компілятор не міг визначитися, і обидві інтерпретації можуть створити альтернативні діючі програми під час створення

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

Друкується 0при пропуску templateраніше, f<int()>але 1при його вставці. Я залишаю це вправою, щоб зрозуміти, що робить код.


3
Тепер це диявольський приклад!
Matthieu M.

1
Я не можу відтворити поведінку, яку ви описуєте в Visual Studio 2013. Вона завжди дзвонить f<U>і завжди друкує 1, що для мене цілком логічно. Я досі не розумію, для чого templateпотрібне ключове слово та яке значення воно має.
Violet Giraffe

@Violet компілятор VSC ++ не відповідає компілятору C ++.
Потрібне

1
Ця відповідь пояснює, чому templateпотрібно: stackoverflow.com/questions/610245/…, не покладаючись лише на стандартні терміни, які важко зрозуміти. Будь ласка, повідомте, якщо щось у цій відповіді все ще бентежить.
Йоханнес Шауб - літб,

@ JohannesSchaub-litb: Дякую, чудова відповідь. Виявляється, я вже читав це раніше, бо воно вже було моїм голосом. Мабуть, моя пам’ять - це я.
Violet Giraffe

12

Вставте його безпосередньо перед точкою, де знаходиться каретка:

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

Редагувати: причина цього правила стає зрозумілішою, якщо ви думаєте як компілятор. Зазвичай компілятори дивляться вперед лише на один-два лексеми, і зазвичай не «дивляться вперед» на решту виразу. [Редагувати: див. Коментар] Причина ключового слова така ж, як і те, чому вам потрібно typenameключове слово для вказівки імен залежних типів: воно повідомляє компілятору "привіт, ідентифікатор, який ви збираєтесь побачити, це назва шаблону, а не ім'я статичного члена даних, за яким слідує знак менше ".


1
Я ніколи б не міг цього здогадатися ... але дякую ;-). явно завжди є про що дізнатись про C ++!

3
Навіть маючи нескінченний погляд уперед, вам все одно знадобиться template. Бувають випадки, коли як з, так і без нього templateвиходять діючі програми з різною поведінкою. Отже, це не лише синтаксична проблема ( t->f0<int()>(0)діє синтаксично як для версії списку аргументів менше, ніж для шаблону).
Йоханнес Шауб - Litb

@Johannes Schaub - litb: Правильно, отже, це більше проблема присвоєння виразу послідовному семантичному значенню, ніж дивитися вперед.
Даг

11

Витяг із шаблонів C ++

Побудова .template Дуже схожа проблема була виявлена ​​після введення typename. Розглянемо наступний приклад, використовуючи стандартний тип бітсету:

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

Дивною конструкцією в цьому прикладі є .template. Без цього додаткового використання шаблону компілятор не знає, що маркер менше <(), що слідує, насправді не є «меншим», а є початком списку аргументів шаблону. Зауважте, що це проблема лише в тому випадку, якщо конструкція до періоду залежить від параметра шаблону. У нашому прикладі параметр bs залежить від параметра шаблону N.

На закінчення нотація .template (та подібні нотації, такі як -> шаблон) повинна використовуватися лише всередині шаблонів і лише в тому випадку, якщо вони відповідають чомусь, що залежить від параметра шаблону.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.