c++ - ultima - ¿Por qué mis expresiones SFINAE ya no funcionan con GCC 8.2?
gnu c compiler gcc (3)
Esta es mi opinión sobre ella. En resumen, clang es correcto y gcc tiene una regresión.
Tenemos de acuerdo con [temp.deduct]p7 :
La sustitución se produce en todos los tipos y expresiones que se utilizan en el tipo de función y en las declaraciones de parámetros de la plantilla. [...]
Esto significa que la sustitución tiene que suceder ya sea que el paquete esté vacío o no. Debido a que todavía estamos en el contexto inmediato, esto es compatible con SFINAE.
A continuación, tenemos que un parámetro variadic se considera un parámetro de plantilla real; de [temp.variadic]p1
Un paquete de parámetros de plantilla es un parámetro de plantilla que acepta cero o más argumentos de plantilla.
y [temp.param]p2 dice qué parámetros de plantilla que no son de tipo están permitidos:
Un parámetro de plantilla que no sea de tipo tendrá uno de los siguientes tipos (opcionalmente calificados para cv):
un tipo que es literal, tiene una fuerte igualdad estructural ([class.compare.default]), no tiene subobjetos mutables o volátiles, y en el que si hay un operador miembro predeterminado <=>, se declara público,
un tipo de referencia lvalue,
un tipo que contiene un tipo de marcador de posición ([dcl.spec.auto]), o
un marcador de posición para una clase de clase deducida ([dcl.type.class.deduct]).
Tenga en cuenta que el void
no se ajusta a la factura, su código (como se publicó) está mal formado.
Recientemente actualicé GCC a 8.2, y la mayoría de mis expresiones SFINAE han dejado de funcionar.
Lo siguiente está algo simplificado, pero demuestra el problema:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
C ++ (gcc) - Pruébelo en línea
C ++ (clang) - Pruébalo en línea
Las versiones anteriores de GCC (desafortunadamente no recuerdo la versión exacta que había instalado anteriormente), así como Clang compilan el código anterior muy bien, pero GCC 8.2 da un error que indica:
: In function ''int main()'': :29:19: error: call of overloaded ''test()'' is ambiguous c.test(); ^ :12:10: note: candidate: ''void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]'' void test() { ^~~~ :22:10: note: candidate: ''void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]'' void test() { ^~~~ :30:25: error: call of overloaded ''test()'' is ambiguous c.test(); ^ :12:10: note: candidate: ''void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]'' void test() { ^~~~ :22:10: note: candidate: ''void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]'' void test() {
Como suele ser el caso cuando diferentes compiladores y versiones de compilador manejan el mismo código de manera diferente, asumo que estoy invocando un comportamiento indefinido. ¿Qué tiene que decir la norma sobre el código anterior? ¿Qué estoy haciendo mal?
Nota: La pregunta no es para solucionar este problema, hay varios que vienen a la mente. La pregunta es por qué esto no funciona con GCC 8: ¿no está definido por el estándar o es un error del compilador?
Nota 2: dado que todos estaban saltando el tipo de void
predeterminado de std::enable_if
, en su std::enable_if
, he cambiado la pregunta para usar int
. El problema permanece.
Nota 3: informe de error GCC creado
No soy un abogado de idiomas, pero ¿no puedo relacionar de alguna manera la siguiente cita con el problema?
[temp.deduct.type/9] : Si Pi es una expansión de paquete, entonces el patrón de Pi se compara con cada argumento restante en la lista de argumentos de plantilla de A. Cada comparación deduce los argumentos de plantilla para las posiciones subsiguientes en los paquetes de parámetros de plantilla expandidos por pi.
Me parece que dado que no hay ningún argumento restante en la lista de argumentos de la plantilla , entonces no hay comparación del patrón (que contiene enable_if
). Si no hay comparación, entonces tampoco hay deducción y la sustitución se produce después de la deducción, creo. En consecuencia, si no hay sustitución, no se aplica SFINAE.
Por favor, corríjame si estoy equivocado. No estoy seguro de si este párrafo en particular se aplica aquí, pero hay más reglas similares con respecto a la expansión del paquete en [temp.deduct]. Además, esta discusión puede ayudar a alguien con más experiencia a resolver todo el problema: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A .
Respuesta parcial: use typename = typename enable_if<...>, T=0
con diferentes T
s:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename = typename std::enable_if_t<
std::is_const<typename std::remove_reference<U>::type>::value
>, int = 0
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename = typename std::enable_if_t<
!std::is_const<typename std::remove_reference<U>::type>::value
>, char = 0
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
( demo )
Aún tratando de averiguar qué diablos hace std::enable_if<...>::type...
significa saber que el tipo predeterminado es void
.