¿Debería compilarse el siguiente código según el estándar C++?
templates gcc (2)
#include <type_traits>
template <typename T>
struct C;
template<typename T1, typename T2>
using first = T1;
template <typename T>
struct C<first<T, std::enable_if_t<std::is_same<T, int>::value>>>
{
};
int main ()
{
}
Resultados de la compilación por diferentes compiladores:
MSVC:
error C2753: ''C'': la especialización parcial no puede coincidir con la lista de argumentos para la plantilla primaria
gcc-4.9:
error: la especialización parcial ''C'' no especializa ningún argumento de plantilla
clang todas las versiones:
error: la especialización parcial de la plantilla de clase no especializa ningún argumento de plantilla; para definir la plantilla principal, elimine la lista de argumentos de la plantilla
gcc-5 +: compila con éxito
Y, además, quiero señalar que una especialización trivial como:
template<typename T>
struct C<T>
{
};
con éxito no puede ser compilado por gcc. Entonces parece que se da cuenta de que la especialización en mi ejemplo original no es trivial. Entonces mi pregunta es: ¿el patrón como este está explícitamente prohibido por C ++ estándar o no?
Creo que podrías simplificar tu código, esto no tiene nada que ver con type_traits. Obtendrás los mismos resultados con el siguiente:
template <typename T>
struct C;
template<typename T>
using first = T;
template <typename T>
struct C<first<T>> // OK only in 5.1
{
};
int main ()
{
}
Verifique el compilador en línea (compila bajo 5.1 pero no con 5.2 o 4.9 por lo que probablemente sea un error) - https://godbolt.org/g/iVCbdm
Creo que int GCC 5 se movieron alrededor de la funcionalidad de la plantilla e incluso es posible crear dos especializaciones del mismo tipo. Se compilará hasta que intente usarlo.
template <typename T>
struct C;
template<typename T1, typename T2>
using first = T1;
template<typename T1, typename T2>
using second = T2;
template <typename T>
struct C<first<T, T>> // OK on 5.1+
{
};
template <typename T>
struct C<second<T, T>> // OK on 5.1+
{
};
int main ()
{
C<first<int, int>> dummy; // error: ambiguous template instantiation for ''struct C<int>''
}
Puede estar de alguna manera relacionado con el soporte adicional para las plantillas de variables de C ++ 14. https://isocpp.org/files/papers/N3651.pdf
El párrafo crucial es [temp.class.spec] / (8.2) , que requiere que la especialización parcial sea más especializada que la plantilla primaria. Lo que Clang realmente se queja es que la lista de argumentos es idéntica a la plantilla primaria: esto ha sido eliminado de [temp.class.spec] / (8.3) por la emisión 2033 (que establecía que el requisito era redundante) bastante recientemente, así que no lo hizo ha sido implementado en Clang todavía. Sin embargo, aparentemente se implementó en GCC, dado que acepta su fragmento; incluso compila lo siguiente , quizás por la misma razón que compila tu código (sino que también funciona desde la versión 5 en adelante):
template <typename T>
void f( C<T> ) {}
template <typename T>
void f( C<first<T, std::enable_if_t<std::is_same<T, int>::value>>> ) {}
Es decir, reconoce que las declaraciones son distintas, por lo que debe haber implementado alguna resolución del tema 1980 . Sin embargo, no encuentra que la segunda sobrecarga sea más especializada (consulte el enlace de Wandbox), que es inconsistente, porque debería haber diagnosticado su código de acuerdo con la restricción mencionada en (8.2).
Podría decirse que la redacción actual hace que el trabajo de ordenamiento parcial de su ejemplo sea el deseado † : [temp.deduct.type] / 1 menciona que, en deducción de los tipos,
Los argumentos de plantilla se pueden deducir en varios contextos diferentes, pero en cada caso un tipo que se especifica en términos de parámetros de plantilla (llámelo
P
) se compara con un tipo real (llámeloA
), y se intenta encontrar el argumento de la plantilla valores [...] que haránP
, después de la sustitución de los valores deducidos (llámalo el deducidoA
), compatible conA
Ahora vía [temp.alias] / 3 , esto significaría que durante el paso de ordenamiento parcial en el que la plantilla de función de especialización parcial es la plantilla de parámetro, la sustitución en is_same
produce falso (ya que las implementaciones de biblioteca común solo usan una especialización parcial que debe fallar ) y enable_if
falla. ‡ Pero esta semántica no es satisfactoria en el caso general, porque podríamos construir una condición que generalmente tenga éxito, por lo que un tipo sintetizado único la cumple, y la deducción tiene éxito en ambos sentidos.
Es de suponer que la solución más simple y robusta es ignorar los argumentos descartados durante el ordenamiento parcial (lo que hace que su ejemplo sea mal formado). También se puede orientar hacia los comportamientos de las implementaciones en este caso (análoga a la cuestión 1157 ):
template <typename...> struct C {};
template <typename T>
void f( C<T, int> ) = delete;
template <typename T>
void f( C<T, std::enable_if_t<sizeof(T) == sizeof(int), int>> ) {}
int main() {f<int>({});}
Tanto Clang como GCC diagnostican esto llamando a la función eliminada, es decir, acuerdan que la primera sobrecarga es más especializada que la otra. La propiedad crítica de # 2 parece ser que el segundo argumento de la plantilla es dependiente pero T
aparece únicamente en contextos no deducidos (si cambiamos int
a T
en el n. ° 1, nada cambia). Entonces, ¿podríamos usar la existencia de argumentos de plantilla descartados (y dependientes?) Como desempates: de esta manera no tenemos que razonar sobre la naturaleza de los valores sintetizados, que es el status quo, y también obtener un comportamiento razonable en su caso , que estaría bien formado.
† @TC mencionó que las plantillas generadas a través de [temp.class.order] se interpretarían actualmente como una entidad declarada de multiplicación, de nuevo, consulte el número 1980 . Eso no es directamente relevante para el estándar en este caso, porque la redacción nunca menciona que estas plantillas de funciones están declaradas, y mucho menos en el mismo programa; simplemente los especifica y luego vuelve al procedimiento para plantillas de funciones.
‡ No está del todo claro con qué profundidad se requieren las implementaciones para realizar este análisis. El problema 1157 demuestra qué nivel de detalle se requiere para determinar "correctamente" si el dominio de una plantilla es un subconjunto propio de la otra. No es práctico ni razonable implementar órdenes parciales para ser tan sofisticado. Sin embargo, la sección de notas a pie solo muestra que este tema no está necesariamente especificado, sino que es defectuoso.