C ++ - Почему здесь требуется ключевое слово 'template'?

9

У меня есть следующий код:

template <typename TC>
class C
{
    struct S
    {
        template <typename TS>
        void fun() const
        {}
    };

    void f(const S& s)
    {
        s.fun<int>();
    }
};

// Dummy main function
int main()
{
    return 0;
}

При сборке с gcc 9.2 и clang (9.0) я получаю ошибку компиляции из-за templateключевого слова, необходимого для вызова fun. Clang показывает:

error: use 'template' keyword to treat 'fun' as a dependent template name
        s.fun<int>();
          ^
          template 

Я не понимаю, почему компилятор считает funзависимое имя в контексте f, так как fэто не сам шаблон. Если я заменю Cобычный класс вместо шаблона, ошибка исчезнет; однако, я не понимаю, почему в первую очередь должна быть ошибка, поскольку она не зависит и Sне fзависит TC.

Как ни странно, MSVC 19.22 компилирует это просто отлично.


нота

Перед голосованием закройте как dupe of Где и почему я должен поставить ключевые слова "template" и "typename"? пожалуйста, подумайте, что это особый случай, когда даже если Sэто действительно зависимое имя, в контексте fоно не будет зависимым, если бы не тот факт, что они являются членами текущего экземпляра.

Мартин
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Бхаргав Рао

Ответы:

10

Рассмотрим :

template<typename T>
struct C
{
    struct S
    {
        int a = 99;
    };

    void f(S s, int i)
    {
        s.a<0>(i);
    }
};

template<>
struct C<long>::S
{
    template<int>
    void a(int)
    {}
};

int main()
{
    C<int>{}.f({}, 0); // #1
    C<long>{}.f({}, 0); // #2
}

s.a<0>(i)анализируется как выражение, содержащее две операции сравнения <и >, и это нормально для # 1, но не для # 2.

Если это изменено , чтобы s.template a<0>(i)затем # 2 ОК и # 1 не удается. Таким образом, templateключевое слово здесь никогда не бывает лишним.

MSVC способен интерпретировать выражение s.a<0>(i)в одной и той же программе в обоих направлениях. Но это не правильно в соответствии со Стандартом; каждое выражение должно иметь только один синтаксический анализ, с которым должен работать компилятор.

ecatmur
источник
Я до сих пор не до конца понимаю это. Ваш пример показывает, что в этом случае вы используете либо специализацию, Cлибо другую, но вы никогда не сможете создать оба экземпляра. templateКлючевое слово здесь еще ненужной ИМХО, потому который Sполучает взял зависит от того , Cинстанцировании. Без templateключевого слова вы сможете создать оба экземпляра, и поведение f будет отличаться для каждого экземпляра.
Мартин
2
@ Суть в том, что каждый токен должен иметь только одну синтаксическую роль в исходном файле. Например, токен не <может быть оператором сравнения в одном экземпляре шаблона и открывающей угловой скобкой в ​​другом экземпляре. Это необходимо для того, чтобы компиляторы могли анализировать шаблоны в AST (с заполнителями для типов шаблонов).
Ecatmur
В этом есть смысл. Спасибо!
Мартин
7

funможет или не может быть функцией шаблона (или может не существовать вообще) в зависимости от параметра шаблона class C.

Это потому, что вы можете специализироваться S(без специализации C):

template <> struct C<int>::S {};

Поскольку компилятор хочет знать, funявляется ли шаблон шаблоном при первом просмотре class C(перед заменой параметра шаблона), templateэто необходимо.

HolyBlackCat
источник
1
ум ... взорван ...
Болов
Таким образом, последующим Sшагом является возможность доступа к этим новым определениям f. Если они не могут, иметь это ограничение не имеет смысла, потому fчто не сможет их увидеть в любом случае.
Мартин
@Martin И с GCC, и с Clang , и с MSVC все- fтаки нашли.
HolyBlackCat
@bolov Да, ты можешь специализировать много разных вещей .
HolyBlackCat