comportamiento diferente de g++ y clang++ con parámetro de plantilla integral


Tengo el siguiente código de 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 g++ 4.8.2 falla la aserción estática

$ 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 es correcto en este caso?

Author: sawyer, 2013-11-28

2 answers

La sorpresa

Este es un sutil Clang bug, profundamente enterrado en el Estándar. El problema es que en casi todos los casos, los argumentos que no son de tipo template se pueden convertir al tipo del parámetro template . 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 de argumento de plantilla [temp.deduct] / 2 2nd bullet

Arguments Los argumentos no-tipo deben coincidir con los tipos del no-tipo correspondiente parámetros de plantilla, o deben ser convertibles a los tipos de parámetros correspondientes distintos del tipo especificados en 14.3.2, de lo contrario la deducción de tipo falla.

Dado que su plantilla de clase is_int<T> tiene una especialización parcial, necesitamos mirar

14.5.5.1 Coincidencia de especializaciones parciales de plantilla de clase [temp.clase.spec.match]

1 Cuando una clase plantilla se utiliza en un contexto que requiere un instanciación de la clase, es necesario determinar si el la instanciación debe generarse utilizando la plantilla principal o una de las especializaciones parciales. Esto se hace haciendo coincidir la plantilla argumentos de la clase plantilla especialización con la plantilla lista de argumentos de las especializaciones parciales .

2 Una especialización parcial coincide con un argumento de plantilla real dado lista si la plantilla argumentos de la especialización parcial pueden ser deducido de la lista de argumentos de plantilla actual (14.8.2).

Así que parece que podemos proceder a la cita anterior de 14.8.2/2 2nd bullet y coincidir con la segunda especialización (aunque en ese caso habría que jugar un juego de resolución de sobrecarga aún más complicado).

La resolución

Sin embargo, resulta (como menciona @DyP en los comentarios) que otra cláusula en el Estándar reemplaza esto:

14.8.2.5 Deducir argumentos de plantilla de un tipo [temp.deducir.type]

17 Si, en la declaración de una plantilla de función con un template-parameter, el templateparameter no de tipo se utiliza en un expresión en la lista de parámetros de función y, si el template-argument is deduced, el tipo template-argument coincidirá el tipo de la plantilla-parámetro exactamente , excepto que un template-argumento deducido de una matriz enlazada puede ser de cualquier integral tipo. [ 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 vs long long) que el parámetro formal de plantilla no tipo de la plantilla de clase Int.

Puede resolver esto dando al parámetro de plantilla no tipo N en la especialización parcial de is_int el mismo tipo que el parámetro no tipo N en la plantilla primaria Int.

template <IntType N>
//        ^^^^^^^^         
struct is_int<Int<N>> : std::true_type {};

Ejemplo En Vivo.

 25
Author: TemplateRex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-29 07:49:48

Clang está siendo inconsistente. Ya que acepta su código , espero que el siguiente código debe salir f(Int<long long>) en 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, produce esta (demo en línea):

f(T)

Que muestra Int<0> NO coincide con la segunda sobrecarga que acepta el argumento como Int<N>. Si eso es así, entonces ¿por qué coincide con Int<N> cuando se usa como argumento de plantilla para la plantilla de clase (en su caso)?

Mi conclusión:

  • Si Clang es correcta en mi caso, entonces es incorrecta en su caso.
  • Si Clang es correcto en su caso, entonces es incorrecto en mi caso.

De cualquier manera, Clang parece tener bug.

GCC, por otro lado, es consistente al menos. Eso no prueba sin embargo que no tiene bug - podría significar que tiene bug en ambos casos! A menos que alguien venga con el standardese y mostrando tiene bug también, voy a confiar en GCC en este caso.

 10
Author: Nawaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-28 14:36:30