C ++ - ¿Por qué se requiere la palabra clave 'plantilla' aquí?

9

Tengo el siguiente código:

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

Al compilar esto con gcc 9.2 y clang (9.0), obtengo un error de compilación debido a que templatese requiere la palabra clave para invocar fun. Clang muestra:

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

No entiendo por qué el compilador piensa que funes un nombre dependiente en el contexto de f, ya fque no es una plantilla en sí. Si cambio Ca ser una clase regular en lugar de una plantilla, el error desaparece; Sin embargo, no veo por qué debería haber un error en primer lugar, ya que Sni fdependen ni dependen de él TC.

Por extraño que parezca, MSVC 19.22 compila esto muy bien.


Nota

Antes de votar para cerrar como engañado de ¿Dónde y por qué tengo que poner las palabras clave "plantilla" y "nombre de tipo"? tenga en cuenta que este es un caso especial en el que incluso si de Shecho es un nombre dependiente, en el contexto de feste no sería dependiente si no fuera por el hecho de que son miembros de la instanciación actual.

Martín
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Bhargav Rao

Respuestas:

10

Considera :

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)se analiza como una expresión que contiene dos operaciones de comparación <y> , y esto está muy bien para # 1, pero falla por # 2.

Si esto se cambia a s.template a<0>(i)# 2, entonces está bien y # 1 falla. Por lo tanto, latemplate palabra clave nunca es redundante aquí.

MSVC es capaz de interpretar la expresión en s.a<0>(i)ambos sentidos dentro del mismo programa. Pero esto no es correcto según la Norma; cada expresión debe tener solo un análisis para que el compilador lo maneje.

ecatmur
fuente
Todavía no entiendo completamente esto. Su ejemplo muestra que, en este caso, utiliza una especialización Cu otra, pero nunca puede crear una instancia de ambas. La templatepalabra clave aquí sigue siendo innecesaria en mi humilde opinión, porque lo que Sse elige depende de qué Cinstancia. Sin la templatepalabra clave, podría crear una instancia de ambos, y el comportamiento de f sería diferente para cada instancia.
Martin
2
@Martin el punto es que cada token debe tener solo un rol sintáctico en el archivo fuente. Por ejemplo, no está bien que el token <sea ​​un operador de comparación en una instanciación de plantilla y un paréntesis angular de apertura en otra instanciación. Esto es para garantizar que los compiladores puedan analizar las plantillas en un AST (con marcadores de posición para los tipos de plantillas).
ecatmur
Eso tiene sentido. ¡Gracias!
Martin
7

fun puede o no ser una función de plantilla (o puede no existir en absoluto) dependiendo del parámetro de plantilla de class C .

Eso es porque puedes especializarte S(sin especializarte C):

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

Debido a que el compilador quiere saber si funes una plantilla o no cuando la mira por primera vez class C(antes de sustituir el parámetro de la plantilla), templatees obligatorio.

HolyBlackCat
fuente
1
mente ... soplado ...
bolov
El seguimiento de esto, entonces, es si Salguna vez se puede acceder a estas nuevas definiciones de f. Si no pueden, tener esta restricción no tiene sentido porque fno podrá verlos de todos modos.
Martin
@Martin Con GCC, Clang y MSVC flo encuentra.
HolyBlackCat
@bolov Sí, puedes especializarte en muchas cosas diferentes .
HolyBlackCat