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


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.