c++ - meaning - Resultados de especialización parcial de clase anidada extraña en gcc y clang



where is clang installed (1)

Mientras escribía una pequeña biblioteca de metaprogramas de plantilla para uso personal, encontré un problema interesante.

Como estaba reutilizando algunas especializaciones parciales para algunas metafunciones, decidí colocarlas en una clase de plantilla común y usar etiquetas junto con la especialización parcial anidada para proporcionar las diferencias de comportamiento.

El problema es que estoy obteniendo resultados sin sentido (para mí). Aquí hay un ejemplo mínimo que muestra lo que estoy tratando de hacer:

#include <iostream>
#include <cxxabi.h>
#include <typeinfo>

template <typename T>
const char * type_name()
{
    return abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
}

template <typename... Args>
struct vargs {};

namespace details   
{
    template <typename K>
    struct outer
    {
        template <typename Arg>
        struct inner
        {
            using result = Arg;
        };
    };
}

struct tag {};

namespace details
{
    template <>
    template <typename Arg, typename... Args>
    struct outer<tag>::inner<vargs<Arg, Args...>>
    {
        using result = typename outer<tag>::inner<Arg>::result;
    };
}

template <typename T>
using test_t = typename details::outer<tag>::inner<T>::result;

int main()
{
    using t = test_t<vargs<char, int>>;
    std::cout << type_name<t>() << '\n';
    return 0;
}

Estoy obteniendo vargs<char, int> como salida cuando uso la versión 5.1.0 de gcc y la tag cuando uso la versión 3.6.0 de clang. Mi intención era que el fragmento de código anterior imprimiera un char así que estoy bastante desconcertado por estos resultados.

¿Es legal el código anterior o muestra un comportamiento indefinido? Si es legal, ¿cuál es el comportamiento esperado según el estándar?


Su código es correcto; Las plantillas parciales implícitamente creadas de instancia de clase plantilla de clase de clase de miembro están destinadas a ser permitidas por el Estándar, siempre que se definan con la suficiente anticipación.

Primero, probemos con un ejemplo mínimo, señalando que no hay nada aquí que requiera C ++ 11:

template<class T> struct A {
  template<class T2> struct B { };
};
// implicitly instantiated class template member class template partial specialization
template<> template<class T2>
  struct A<short>::B<T2*> { };
A<short>::B<int*> absip;    // uses partial specialization?

Como se señaló en otra parte, MSVC e ICC utilizan la especialización parcial como se esperaba; clang selecciona la especialización parcial pero desordena sus parámetros de tipo, aliasing T2 a short vez de int ; y gcc ignora por completo la especialización parcial.

Por qué se permite la especialización parcial de la plantilla de clase de miembro de la clase de clase implícitamente instanciada fuera de clase

En pocas palabras, ninguno de los lenguajes que permiten otras formas de plantilla de clase. Las definiciones de plantilla de clase excluyen la especialización parcial de la clase de clase de clase de miembro implícitamente instanciada. En [temp.mem] , tenemos:

1 - Una plantilla puede ser declarada dentro de una clase o plantilla de clase; tal plantilla se llama una plantilla miembro. Una plantilla miembro se puede definir dentro o fuera de su definición de clase o definición de plantilla de clase. [...]

Una especialización parcial de plantilla de clase es una declaración de plantilla ( [temp.class.spec] / 1). En el mismo párrafo, hay un ejemplo de especialización parcial de plantilla de clase de clase no especializada fuera de clase ( [temp.class.spec] / 5):

template<class T> struct A {
  struct C {
    template<class T2> struct B { };
  };
};
// partial specialization of A<T>::C::B<T2>
template<class T> template<class T2>
  struct A<T>::C::B<T2*> { };
A<short>::C::B<int*> absip; // uses partial specialization

No hay nada aquí que indique que el ámbito adjunto no puede ser una especialización implícita de la plantilla de clase adjunta.

De manera similar, hay ejemplos de la especialización parcial de la clase de la clase de la plantilla de clase de clase y la especialización completa de la plantilla de la clase de la clase de clase de instancia implícitamente instanciada ( [temp.class.spec.mfunc] / 2):

template<class T> struct A {
  template<class T2> struct B {}; // #1
  template<class T2> struct B<T2*> {}; // #2
};
template<> template<class T2> struct A<short>::B {}; // #3
A<char>::B<int*> abcip; // uses #2
A<short>::B<int*> absip; // uses #3
A<char>::B<int> abci; // uses #1

(clang (a partir de 3.7.0-svn235195) hace que el segundo ejemplo sea incorrecto; selecciona # 2 en lugar de # 3 para el absip ).

Si bien esto no menciona explícitamente la especialización parcial de la plantilla de clase de miembro de la plantilla de clase implícitamente instanciada de clase, tampoco la excluye; la razón por la que no está aquí es que es irrelevante para el punto en particular que se está haciendo, que es sobre qué plantilla primaria o especializaciones de plantilla parcial se consideran para una especialización particular.

Por [temp.class.spec] :

6 - [...] cuando se usa el nombre de la plantilla principal, también se consideran las especializaciones parciales de la plantilla primaria previamente declaradas.

En el ejemplo mínimo anterior, A<short>::B<T2*> es una especialización parcial de la plantilla primaria A<short>::B por lo que debe considerarse.

¿Por qué podría no estar permitido?

En otra discusión, hemos visto mencionar que la creación de instancias implícita (de la plantilla de clase adjunta) podría dar lugar a la creación de instancias implícitas de la definición de la especialización de la plantilla primaria, lo que resultaría en un programa NDR mal formado, es decir, UB; [templ.expl.spec] :

6 - Si una plantilla, una plantilla miembro o un miembro de una plantilla de clase está explícitamente especializada, entonces esa especialización se declarará antes del primer uso de esa especialización que causaría una instanciación implícita, en cada unidad de traducción en la que dicha el uso ocurre no se requiere diagnóstico [...]

Sin embargo, aquí la plantilla de clase miembro de la plantilla de clase no se usa antes de ser instanciada.

Lo que piensan otras personas

En DR1755 (activo) , el ejemplo dado es:

template<typename A> struct X { template<typename B> struct Y; };
template struct X<int>;
template<typename A> template<typename B> struct X<A>::Y<B*> { int n; };
int k = X<int>::Y<int*>().n;

Esto se considera problemático solo desde el punto de vista de la existencia de la segunda línea que crea una instancia de la clase envolvente. No hubo ninguna sugerencia del remitente (Richard Smith) o del CWG de que esto podría ser inválido incluso en ausencia de la segunda línea.

En n4090 , el ejemplo dado es:

template<class T> struct A {
  template<class U> struct B {int i; }; // #0
  template<> struct B<float**> {int i2; }; // #1
  // ...
};
// ...
template<> template<class U> // #6
struct A<char>::B<U*>{ int m; };
// ...
int a2 = A<char>::B<float**>{}.m; // Use #6 Not #1

Aquí, la pregunta planteada es de prioridad entre una plantilla de clase de miembro de la clase de clase de especialización completa y una especialización parcial de clase de miembro de clase de clase de instanciación de clase; no hay ninguna sugerencia de que #6 no sería considerado en absoluto.





template-meta-programming