c++ - programacion - (¿Error GCC?) Conversión implícita a una clase derivada
la herencia es un modo de (1)
He encontrado un problema con la conversión implícita en C ++. El siguiente es un ejemplo mínimo:
struct A {
virtual void f()=0; // abstract
};
struct Ad : A {
virtual void f() {} // not abstract
};
struct B {
operator Ad () const { return Ad(); }
};
void test(A const &lhs) {}
int main()
{
B b;
test(b);
}
Lo que me gustaría que el compilador hiciera es: convertir b en una variable de tipo Ad (usando la conversión definida en B) y pasar el resultado a prueba . Sin embargo, el código anterior no se compila en GCC (con C ++ 11 habilitado), el resultado es No se puede asignar un objeto de tipo abstracto ''A'' .
Algunas cosas a tener en cuenta:
- Clang compila esto.
- Si haces A no-abstracto cambiando
f()=0;
af() {}
, el código funciona bien. - El compilador encuentra el operador de conversión (como lo indica 2), pero no hace lo que me gustaría que hiciera.
(Todas las citas de N4140, C ++ 14 FD)
TL; DR: El código está bien formado, esto es (o fue) un error de GCC.
Las reglas para la inicialización de referencia están cubiertas en [dcl.init.ref] / 5. Primero te mostraré la viñeta que no lo cubre; si quieres omitirlo, dirígete directamente a la tercera cita.
De lo contrario, la referencia será una referencia lvalue a un tipo const non-volátil (es decir, cv1 será
const
), o la referencia será una referencia rvalue.
- Si la expresión del inicializador
- es un valor x (pero no un campo de bits), prvalue de clase, prvalue de matriz o lvalue de función y " cv1
T1
" es compatible con referencia con " cv2T2
", o- tiene un tipo de clase (es decir,
T2
es un tipo de clase), dondeT1
no está relacionado con la referencia aT2
, y puede convertirse en un valor x , prvalue de clase o valor de función de tipo " cv3T3
", donde " cv1T1
" es de referencia compatible con " cv3T3
" (ver 13.3.1.6) ,entonces la referencia está vinculada al valor de la expresión de inicializador en el primer caso y al resultado de la conversión en el segundo caso (o, en cualquier caso, a un subobjeto de clase base apropiado).
Y la compatibilidad de referencia se define en [dcl.init.ref] / 4 1 .
Ahora considere el 13.3.1.6 vinculado:
En las condiciones especificadas en 8.5.3, una referencia puede vincularse directamente a un valor pL o un prvalor de clase que es el resultado de aplicar una función de conversión a una expresión de inicializador. La resolución de sobrecarga se utiliza para seleccionar la función de conversión que se invocará. Suponiendo que " cv1
T
" es el tipo subyacente de la referencia que se está inicializando, y " cvS
" es el tipo de expresión del inicializador, con un tipo de claseS
a, las funciones candidatas se seleccionan de la siguiente manera:
- Se consideran las funciones de conversión de
S
y sus clases base. Esas funciones de conversión no explícitas que no están ocultas enS
y producen el tipo "referencia lvalue a cv2T2
" ( al inicializar una referencia lvalue o una referencia rvalue a la función) o " cv2T2
" [..], donde " cv1T
" es compatible con la referencia (8.5.3) con " cv2T2
", son funciones candidatas. Para la inicialización directa, [..].
Como puede ver, su función de conversión no es un candidato después de este párrafo. Por lo tanto, la siguiente viñeta en [dcl.init] / 5 es aplicable:
De otra manera:
- Si
T1
es un tipo de clase, las conversiones definidas por el usuario se consideran utilizando las reglas para la inicialización de copia de un objeto del tipo " cv1T1
" mediante la conversión definida por el usuario (8.5, 13.3.1.4); el programa está mal formado si la inicialización de copia de no referencia correspondiente no está bien formada. El resultado de la llamada a la función de conversión, como se describe para la inicialización de la copia sin referencia, se usa luego para inicializar directamente la referencia. El programa está mal formado si la inicialización directa no da como resultado un enlace directo o si implica una conversión definida por el usuario.
Tenga en cuenta que la frase " el programa está mal formado si la inicialización de copia de referencia no correspondiente se formaría incorrectamente " puede implicar que, como
B b;
A a = b;
está mal formado, el programa está mal formado. Sin embargo, creo que esto es un defecto o vaguedad en la redacción, y no es la razón por la que GCC no acepta el código. Seguramente, la redacción solo apunta a la inicialización en sí misma, no al hecho de que se puede crear un objeto derivado de tipo T1
(también conocido como A
).
Finalmente 13.3.1.4 acepta nuestra función de conversión:
Suponiendo que " cv1
T
" es el tipo de objeto que se está inicializando, conT
un tipo de clase, las funciones candidatas se seleccionan de la siguiente manera:
- Los constructores de conversión (12.3.1) de
T
son funciones candidatas.- Cuando el tipo de expresión del inicializador es un tipo de clase " cv
S
", se consideran las funciones de conversión no explícitas deS
y sus clases base. [..] Aquellos que no están ocultos dentro deS
y producen un tipo cuya versión cv no calificada es del mismo tipo queT
o es una clase derivada de los mismos son funciones candidatas.
Ahora la última pregunta es si en
A const& ref(Ad());
ref
está vinculado directamente. Y es.
Por lo tanto, la referencia de parámetro original se vincula directamente, y no se debe crear ningún objeto derivado de la mayoría de tipo A
Lo que GCC supuestamente piensa es que un temporal de tipo A
debe inicializarse y la referencia debe estar vinculada a ese temporal. O pedantemente sigue la redacción defectuosa anterior, que es muy poco probable.
1)
Los tipos dados " cv1
T1
" y " cv2T2
", " cv1T1
" están relacionados con la referencia " cv2T2
" siT1
es del mismo tipo queT2
, oT1
es una clase base deT2
. " Cv1T1
" es compatible con la referencia " cv2T2
" siT1
está relacionado con la referencia aT2
y cv1 es la misma calificación cv, o mayor calificación cv que, cv2 .