c++ - compile - llvm 3.7 1
g++ y clang++ comportamiento diferente con parĂ¡metro de plantilla integral (2)
La sorpresa
Este es un error sutil de Clang, profundamente enterrado en el Estándar. El problema es que en casi todos los casos, los argumentos de plantilla sin tipo se pueden convertir al tipo del parámetro de plantilla. Por ejemplo, la expresión Int<0>
tiene un argumento literal int
de valor 0
que se está convirtiendo al tipo unsigned long long
del parámetro de plantilla N
14.8.2 Deducción del argumento de plantilla [temp.deduct] / 2 2nd bullet
- Los argumentos sin tipo deben coincidir con los tipos de los correspondientes parámetros de plantilla sin tipo, o deben ser convertibles a los tipos de los parámetros correspondientes que no sean del tipo especificados en 14.3.2; de lo contrario, el tipo de deducción falla.
Dado que su plantilla de clase is_int<T>
tiene una especialización parcial, tenemos que mirar
14.5.5.1 Coincidencia de especializaciones parciales de plantilla de clase [temp.class.spec.match]
1 Cuando se utiliza una plantilla de clase en un contexto que requiere una instanciación de la clase, es necesario determinar si la instanciación debe generarse utilizando la plantilla primaria o una de las especializaciones parciales. Esto se hace al hacer coincidir los argumentos de plantilla de la especialización de plantilla de clase con las listas de argumentos de plantilla de las especializaciones parciales .
2 Una especialización parcial concuerda con una lista de argumentos de la plantilla real dada si los argumentos de la plantilla de la especialización parcial pueden deducirse de la lista de argumentos de la plantilla real (14.8.2).
Por lo tanto, parece que podemos proceder a la cita anterior de 14.8.2 / 2, segunda viñeta, y hacer coincidir la segunda especialización (aunque en ese caso debería jugarse un juego de resolución de sobrecarga aún más complicado).
La resolución
Sin embargo, resulta (como lo menciona @DyP en los comentarios) que otra cláusula del Estándar reemplaza a esta:
14.8.2.5 Deducir argumentos de plantilla de un tipo [temp.deduct.type]
17 Si, en la declaración de una plantilla de función con un parámetro de plantilla no de tipo, el parámetro de plantilla no de tipo se utiliza en una expresión en la lista de parámetros de función y, si se deduce el argumento de plantilla correspondiente, el argumento de plantilla tipo coincidirá exactamente con el tipo de parámetro de plantilla , excepto que un argumento de plantilla deducido de un conjunto de matrices puede ser de cualquier tipo integral. [Ejemplo:
template<int i> class A { / ... / };
template<short s> void f(A<s>);
void k1() {
A<1> a;
f(a); // error: deduction fails for conversion from int to short
f<1>(a); // OK
}
El resultado es que la especialización parcial de is_int
no se puede deducir porque no toma exactamente el mismo tipo ( unsigned long long
ni de long long
) como el parámetro de la plantilla de tipo no formal de la plantilla de la clase Int
.
Puede resolver esto dando al parámetro de plantilla no de tipo N
en la especialización parcial de is_int
el mismo tipo que el parámetro de no tipo N
en la plantilla principal Int
.
template <IntType N>
// ^^^^^^^^
struct is_int<Int<N>> : std::true_type {};
Tengo el siguiente código C ++ 11.
#include <type_traits>
using IntType = unsigned long long;
template <IntType N> struct Int {};
template <class T>
struct is_int : std::false_type {};
template <long long N>
struct is_int<Int<N>> : std::true_type {};
int main()
{
static_assert (is_int<Int<0>>::value, "");
return 0;
}
Clang ++ 3.3 compila el código pero en la declaración estática de g ++ 4.8.2 falla
$ g++ -std=c++11 main.cpp
main.cpp: In function ‘int main()’:
main.cpp:15:5: error: static assertion failed:
static_assert (is_int<Int<0>>::value, "");
^
$
El problema es causado por diferentes parámetros de plantilla integral. ¿Qué compilador tiene razón en este caso?
Clang está siendo inconsistente. Como acepta su código , estoy esperando que el siguiente código de salida f(Int<long long>)
lugar de f(T)
:
using IntType = unsigned long long;
template <IntType N> struct Int {};
template<typename T>
void f(T) { std::cout << "f(T)" << std::endl; }
template<long long N>
void f(Int<N>) { std::cout << "f(Int<long long>)" << std::endl; }
int main()
{
f(Int<0>{});
}
Pero, sorprendentemente, muestra esto ( demostración en línea ):
f(T)
Eso muestra que Int<0>
NO coincide con la segunda sobrecarga, que acepta el argumento como Int<N>
. Si es así, ¿por qué coincide con Int<N>
cuando se utiliza como argumento de plantilla para la plantilla de clase (en su caso)?
Mi conclusión:
- Si Clang es correcto en mi caso, entonces es incorrecto en su caso.
- Si Clang es correcto en su caso, entonces es incorrecto en mi caso.
De cualquier manera, Clang parece tener errores.
GCC, por otro lado, es consistente al menos. Sin embargo, eso no prueba que no tenga errores, ¡podría significar que tiene errores en ambos casos! A menos que alguien presente la norma y muestre que también tiene errores, voy a confiar en GCC en este caso.