c++ - studio - Clases con operadores de conversión de plantilla y no de plantilla en la condición de instrucción switch
programacion android pdf 2018 (6)
El problema surgió originalmente en esta pregunta . Considere el siguiente código:
class Var
{
public:
operator int () const
{ return 0; }
template <typename T>
operator T () const
{ return T(); }
};
int main()
{
Var v;
switch (v)
{ }
}
Sin el operator int() const { return 0; }
operator int() const { return 0; }
, tanto g ++ como clang reject el código.
Sin embargo, el código anterior, con el operator int()
, es accepted por clang pero rejected por g ++ con el siguiente error:
main.cpp:17:14: error: default type conversion can''t deduce template argument for ''template<class T> Var::operator T() const''
switch (v)
^
¿Qué compilador es correcto?
Aquí están las citas relevantes, pero la respuesta final depende bastante de la interpretación. Ni siquiera puedo decidir sobre un favorito en este momento.
N3797 6.4.2 / 2:
La condición debe ser de tipo integral, tipo de enumeración o tipo de clase. Si es del tipo de clase, la condición se convierte implícitamente de forma contextual (Cláusula 4) a un tipo de integral o enumeración.
4/5:
Ciertas construcciones de lenguaje requieren la conversión a un valor que tenga uno de un conjunto específico de tipos apropiados para la construcción. Se dice que una expresión
e
del tipo de claseE
aparece en dicho contexto se convierte implícitamente en un tipo específicoT
y está bien formada si, y solo si,e
puede convertirse implícitamente en un tipoT
que se determina de la siguiente manera:E
es buscó funciones de conversión cuyo tipo de retorno sea cvT
o referencia a cvT
modo queT
esté permitido por el contexto. Debe haber exactamente una talT
14.5.2 / 6:
No se encuentra una especialización de una función de conversión por búsqueda de nombre. En su lugar, se consideran las plantillas de función de conversión visibles en el contexto del uso. Para cada uno de dichos operadores, si la deducción de argumentos es exitosa (14.8.2.3), la especialización resultante se usa como si se hubiera encontrado por búsqueda de nombre.
14.5.2 / 8:
La resolución de sobrecarga (13.3.3.2) y el ordenamiento parcial (14.5.6.2) se utilizan para seleccionar la mejor función de conversión entre las especializaciones múltiples de plantillas de función de conversión y / o funciones de conversión sin plantilla.
La interpretación 1: 4/5 dice "funciones de conversión", no "funciones de conversión y plantillas de función de conversión". Por lo tanto, Var::operator int() const
es la única opción, y el clang es correcto.
Interpretación 2 [¿débil?]: 14.5.2 nos obliga a comparar la plantilla de la función de conversión por resolución de sobrecarga y orden parcial, en la misma posición inicial que la función de conversión sin plantilla. Esos comparan las especializaciones y funciones de la plantilla de función, no las plantillas de función, por lo que haremos la deducción de argumentos de plantilla. La deducción de argumentos de plantilla para una plantilla de función de conversión requiere un tipo de destino. Aunque usualmente tenemos un tipo de objetivo más claro, en este caso solo intentaremos (en teoría) todos los tipos en el conjunto de tipos permitidos. Pero está claro que la función sin plantilla es una función mejor viable que todas las especializaciones de plantilla, por lo que la resolución de sobrecarga selecciona la función sin plantilla. clang es correcto
Interpretación 3: dado que la resolución de sobrecarga requiere la deducción de los argumentos de la plantilla, y la deducción de los argumentos de la plantilla requiere un tipo de objetivo conocido, la semántica de 4/5 debe considerarse primero, y luego su tipo convertido (si existe) puede usarse para el proceso de resolución de la sobrecarga. 14.5.2 requiere que se considere la plantilla de la función de conversión, pero luego encontramos que hay varios tipos T
válidos para los cuales tenemos una función de conversión a T
[esa función posiblemente sea una especialización de la plantilla de función]. El programa está mal formado y, por lo tanto, g ++ es correcto.
Creo que el clang
correcto aquí.
Podemos ver en el borrador de la sección 6.4.2
estándar de C ++. La declaración del switch que esto implica una conversión implícita en el contexto . El párrafo 2 dice (* énfasis mío en el futuro):
La condición debe ser de tipo integral, tipo de enumeración o tipo de clase. Si es del tipo de clase, la condición se convierte implícitamente de forma contextual (Cláusula 4) a un tipo de integral o enumeración.
Podemos ver que la sección que necesitamos usar es 4
conversiones estándar y el párrafo 5 cubre estos casos, dice:
Ciertas construcciones de lenguaje requieren la conversión a un valor que tenga uno de un conjunto específico de tipos apropiados para la construcción. Se dice que una expresión e del tipo de clase E que aparece en dicho contexto se convierte implícitamente en un tipo específico T y está bien formada si, y solo si, e puede convertirse implícitamente en un tipo T que se determina de la siguiente manera: E es buscó funciones de conversión cuyo tipo de retorno sea cv T o referencia a cv T, de modo que T esté permitido por el contexto. Debe haber exactamente una tal T.
Esto no hace referencia a la sección 8.5
que permite la resolución de sobrecarga al referirse específicamente a la sección 13.3
sin permitir la resolución de sobrecarga que no podemos usar:
template <typename T>
operator T () const
Y por eso no hay ambigüedad.
Tenga en cuenta que esto es diferente del párrafo 4 que cubre las conversiones bool en contextos de if , while , etc ... y dice ( énfasis mío ):
Ciertas construcciones de lenguaje requieren que una expresión se convierta en un valor booleano. Se dice que una expresión e que aparece en dicho contexto se convierte contextualmente a bool y está bien formada si y solo si la declaración es bool t (e); está bien formado, para alguna variable temporal inventada t (8.5).
que específicamente permite la resolución de sobrecarga y se refiere directamente a la sección 13.3
que cubre esto. Es lógico que esté permitido, ya que tenemos un tipo de destino específico que convertir para bool al que no tenemos en el caso del switch .
Por qué
Podemos resolver esto yendo a N3323: una propuesta para modificar ciertas conversiones contextuales de C ++, v3 cubre este problema. Sería difícil citar todo el documento, así que intentaré citar lo suficiente del contexto. Dice:
El contexto en el que aparece una expresión en C ++ a menudo influye en cómo se evalúa la expresión y, por lo tanto, puede imponer requisitos en la expresión para garantizar que dicha evaluación sea posible. [...]
En cuatro casos, el FDIS (N3290) usa un lenguaje diferente para especificar una conversión dependiente del contexto análoga. En esos cuatro contextos, cuando un operando es de tipo de clase, ese tipo debe tener una "función de conversión única no explícita" a un tipo adecuado (específico del contexto). [...]
e incluye:
[stmt.switch] / 2: "La condición será de tipo integral, tipo de enumeración o tipo de clase para la cual existe una función de conversión no explícita a tipo integral o de enumeración (12.3)".
y dice:
El problema principal, en cada uno de los cuatro contextos citados en la Introducción, parece residir en su requisito muy útil, pero muy estricto, que limita una clase a un solo operador de conversión [...]
Otra preocupación es el alcance del calificativo "single" en la redacción actual. ¿Debe haber solo una única función de conversión en la clase, o puede haber varias, siempre y cuando una sola sea apropiada para el contexto?
El lenguaje actual no parece claro en este punto. Tampoco está claro si un operador de conversión que produce una referencia a un tipo apropiado es un operador de conversión apropiado. (Una pregunta sobre este punto se publicó en el Reflector Core el 21/02/2011, pero no se ha respondido en el momento de la redacción de este documento). La práctica actual del compilador parece admitir a dichos operadores, pero el lenguaje actual no lo parece.
y propone:
Para abordar todas estas inquietudes, recomendamos utilizar el enfoque comprobado tipificado por el término convertido contextualmente a bool como se define en [conv] / 3. Por lo tanto, proponemos una adición modesta a [conv] / 3 para definir la conversión contextual a otros tipos específicos, y luego apelamos a esta nueva definición.
y el nuevo lenguaje sería el siguiente;
Ciertas construcciones de otros lenguajes requieren una conversión similar, pero a un valor que tenga uno de un conjunto específico de tipos apropiados para la construcción. Se dice que una expresión e del tipo de clase E que aparece en dicho contexto se convierte implícitamente en un tipo específico T y está bien formada si, y solo si, e puede convertirse implícitamente en un tipo T que se determina de la siguiente manera: E es buscó funciones de conversión cuyo tipo de retorno sea cv T o referencia a cv T, de modo que T esté permitido por el contexto. Debe haber exactamente una tal T.
Nota N3486: Informe del editor de C ++, octubre de 2012 , nos muestra cuándo se incorporó N3323
en el proyecto de norma.
Actualizar
Archivado un informe de error gcc .
Creo que gcc es correcto, pero el estándar es defectuoso.
gcc es correcto porque el estándar obliga a un solo operador de conversión no explícito a tipos integrales o de enumeración para los tipos utilizados en el switch
.
La norma es incorrecta porque la detección de ese caso implica resolver el problema de detención.
operator T
puede tener una cláusula SFINAE de complejidad arbitraria adjunta. El compilador, según el estándar, debe determinar si hay una T
tal que la T
sea una enum
.
template<class...Ts>
struct evil {
enum { bob = 3+sizeof...(Ts) };
};
struct test {
operator int() const { return -1; };
template<class T, typename std::enable_if<T::bob==2>::type* unused=nullptr>
operator T() const { return T::bob; }
};
int main() {
switch( test{} ) {
case -1: std::cout << "int/n"; break;
case 2: std::cout << "bob/n"; break;
default: std::cout << "unexpected/n"; break;
}
}
El código anterior muestra un caso en el que tenemos un número infinito de enum
implícitamente disponibles. Tenemos un operator T
que convertirá al tipo T
si y solo si T::bob==2
. Ahora, no hay tales enum
en nuestro programa (e incluso si elimináramos las 3+
todavía no habría, porque no es una enum class
- se rectifica fácilmente).
Por lo tanto, la test
solo se puede convertir a int
, y como tal, la instrucción switch
debería compilarse. gcc no pasa esta prueba y afirma que el template operator T
hace ambiguo (sin decirnos qué es T
, naturalmente).
Reemplazar el enum type
de enum class type
con el enum type
enum class type
, y eliminar el 3+
hace que la declaración de switch
ilegal según la norma. Pero para que el compilador lo descubra, básicamente tiene que crear una instancia de todas las plantillas posibles en el programa en busca de una enum
secreta con la propiedad en cuestión. Con un poco de trabajo, puedo forzar al compilador a resolver los problemas completos de NP (o, excluyendo las limitaciones del compilador, el problema de detención) para determinar si un programa debe compilarse o no.
No sé cuál debe ser la redacción correcta. Pero la redacción tal como está escrita no es sólida.
En mi hummble opinión y basado en §13.3.3 / 1 La mejor función viable [over.match.best] , el operador de conversión sobrecargado sin plantilla (es decir, operator int() const
) tiene una mayor prioridad en términos de selección de resolución de sobrecarga , que su contraparte de la plantilla (es decir, la template <typename T> operator T () const
).
Por lo tanto, la resolución sobrecargada elegiría correctamente el operator int() const
sobre la template <typename T> operator T () const
ya que es la mejor función viable.
Además, y dado que la versión sin plantilla se elegiría sobre la plantilla uno (es decir, el compilador no materializaría / calificaría la plantilla), la class Var
tendría una función de conversión única y, por lo tanto, el requisito en §6.4.2 / 2 Se cumpliría la instrucción de cambio [stmt.switch] para la conversión integral única.
En consecuencia, Clang tiene razón y GCC está equivocado.
Si estoy leyendo esta sección correctamente en la sobrecarga, Clang es correcto
13.3.3 Mejor función viable [over.match.best]
[...] Dadas estas definiciones, una función viable F1 se define como una función mejor que otra función viable F2 si, para todos los argumentos i, ICSi (F1) no es una secuencia de conversión peor que ICSi (F2), y luego [ ...]
- F1 es una función no de plantilla y F2 es una especialización de plantilla de función o, si no es eso, [...]
El borrador es gratis para leer. No estoy seguro si algún cambio en 13.3.3 se incluyó en la especificación final (no lo he pagado)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
Yo presentaría un error de G ++ :-) Es posible que se devuelvan con una sección diferente del estándar para justificar, pero parece que no cumple con los estándares.
Editar para el comentario de aschepler:
Supongamos que f es un nombre de función sobrecargado. Cuando llama a la función sobrecargada f (), el compilador crea un conjunto de funciones candidatas. Este conjunto de funciones incluye todas las funciones denominadas f a las que se puede acceder desde el punto donde llamó a f (). El compilador puede incluir como una función candidata una representación alternativa de una de esas funciones accesibles llamada f para facilitar la resolución de sobrecarga.
Después de crear un conjunto de funciones candidatas, el compilador crea un conjunto de funciones viables. Este conjunto de funciones es un subconjunto de las funciones candidatas. La cantidad de parámetros de cada función viable concuerda con la cantidad de argumentos que usó para llamar a f ().
6.4.2 / 2 La declaración de switch
(el énfasis es mío)
La condición debe ser de tipo integral, tipo de enumeración o de un tipo de clase para la cual exista una única función de conversión no explícita a tipo integral o de enumeración (12.3). Si la condición es de tipo de clase, la condición se convierte al llamar a esa función de conversión, y el resultado de la conversión se usa en lugar de la condición original para el resto de esta sección.
Así que mi interpretación es que g ++ es correcta aquí.