54

La lectura de algunas preguntas aquí en SO sobre operadores de conversión y constructores me hizo pensar en la interacción entre ellos, es decir, cuando hay una llamada "ambigua". Considere el siguiente código:Operador de conversión frente a operador de conversión: precedencia

class A; 

class B { 
     public: 
     B(){} 

     B(const A&) //conversion constructor 
     { 
       cout << "called B's conversion constructor" << endl; 
     } 
}; 

class A { 
     public: 
     operator B() //conversion operator 
     { 
       cout << "called A's conversion operator" << endl; 
       return B(); 
     } 
}; 

int main() 
{ 
    B b = A(); //what should be called here? apparently, A::operator B() 
    return 0; 
} 

Las pantallas de código por encima de "llamada de un operador de conversión", lo que significa que el operador de conversión se denomina en contraposición al constructor. Si elimina/comenta el código operator B() de A, el compilador cambiará con mucho gusto al uso del constructor (sin ningún otro cambio en el código).

Mis preguntas son:

  1. Dado que el compilador no considera B b = A(); ser una llamada ambigua, tiene que haber algún tipo de prioridad en el trabajo aquí. ¿Dónde exactamente está establecida esta precedencia? (se apreciaría una referencia/cita del estándar C++)
  2. Desde un punto de vista filosófico orientado a objetos, ¿es así como debe comportarse el código? ¿Quién sabe más acerca de cómo un objeto A debe convertirse en un objeto B, A o B? De acuerdo con C++, la respuesta es A - ¿Hay algo en la práctica orientada a objetos que sugiera que este debería ser el caso? Para mí personalmente, tendría sentido en cualquier caso, así que estoy interesado en saber cómo se hizo la elección.

Gracias de antemano

+0

La línea te comento "// constructor de copia" no es un constructor de copia, es un constructor. –

+0

Tienes razón, hice un mal uso del término. Lo he editado. – GRB

Respuesta

42

desea hacer una copia de inicialización, y las funciones que se consideran candidatos para hacer las conversiones en la secuencia de la conversión son funciones de conversión y constructores de conversión. Estos son en su caso

B(const A&) 
operator B() 

Ahora, que son la forma en que se declaran. La resolución de sobrecarga abstrae de eso y transforma a cada candidato en una lista de parámetros que corresponden a los argumentos de la llamada. Los parámetros son

B(const A&) 
B(A&) 

El segundo es porque la función de conversión es una función miembro. El A& es el llamado parámetro de objeto implícito que se genera cuando un candidato es una función miembro. Ahora, el argumento tiene tipo A. Al vincular el parámetro de objeto implícito, una referencia no constante puede enlazar a un valor r. Entonces, otra regla dice que cuando tiene dos funciones viables cuyos parámetros son referencias, entonces el candidato que tenga la menor calificación de const ganará. Es por eso que su función de conversión gana. Intente hacer que operator B sea una función miembro miembro. Notarás una ambigüedad.

Desde un punto de vista filosófico orientado a objetos, ¿es así como debe comportarse el código? ¿Quién sabe más acerca de cómo un objeto A debería convertirse en un objeto B, A o B? Según C++, la respuesta es A - ¿hay algo en la práctica orientada a objetos que sugiera que este debería ser el caso? Para mí personalmente, tendría sentido en cualquier caso, así que estoy interesado en saber cómo se hizo la elección.

Para el registro, si usted hace la función de conversión de un método constante, entonces GCC se eligió el constructor (GCC por lo que parece pensar que B tiene más negocios con él?). Cambie al modo pedante (-pedantic) para que cause un diagnóstico.


Standardese, 8.5/14

De lo contrario (es decir, para los casos de copia de inicialización restantes), las secuencias de conversión definidas por el usuario que pueden convertir desde el tipo de fuente para el tipo de destino o (cuando una conversión función se usa) a una clase derivada del mismo se enumeran como se describe en 13.3.1.4, y el mejor se elige a través de la resolución de sobrecarga (13.3).

Y 13.3.1.4

resolución de sobrecarga se utiliza para seleccionar la conversión definida por el usuario que se invoca. Suponiendo que "CV1 T" es el tipo del objeto que se inicializa, con T un tipo de clase, las funciones candidatas se seleccionan como sigue:

  • 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 de S y sus clases base. Aquellos que no están ocultos dentro de S y producen un tipo cuya versión cv no calificada es del mismo tipo que T o es una clase derivada de los mismos son funciones candidatas. Las funciones de conversión que devuelven "referencia a X" devuelven valores l de tipo X y, por lo tanto, se considera que producen X para este proceso de selección de funciones candidatas.

En ambos casos, la lista de argumentos tiene un argumento, que es la expresión del inicializador. [Nota: este argumento se comparará con el primer parámetro de los constructores y con el parámetro de objeto implícito de las funciones de conversión. ]

Y 13.3.3.2/3

  • secuencia de conversión estándar S1 es una mejor secuencia de la conversión de la secuencia de conversión estándar S2 si [...] S1 y S2 son enlaces de referencia (8.5.3), y los tipos a los que se refieren las referencias son del mismo tipo excepto los cv-calificadores de nivel superior, y el tipo al que la referencia inicializada por S2 se refiere es más cv-calificado que el tipo al que se refiere la referencia inicializada por S1.
+1

Ah, entonces mi problema no era sobre la precedencia entre el constructor y el operador, sino el 'const' -ess de cada uno. Tenías razón, el cambio del 'operador B()' al 'operador B() const' resultó en un error de ambigüedad. – GRB

+1

Qué coincidencia que coloque el mismo "análisis" en otra respuesta mía como un ejemplo anterior: http://stackoverflow.com/questions/1051379/is-there-a-difference-in-c-bet-copy-copy- initialization-and-assignment-initializ/1051468 # 1051468 xD –

+0

Pregunta rápida sobre la primera oración de la respuesta: 'usted copia la inicialización ...'. No veo que se produzca una copia en ninguna parte, es decir, no se construye 'A' a partir de otra' A' (ni una 'B' de otra' B'), así que estoy en lo correcto al decir que literalmente no significa 'copia initialization', sino más bien algo en la línea de 'initialization asignación'? ¡Gracias! –

3

Parece MSVS2008 tiene su propia opinión sobre la selección constructor: se llama constructor de copia en B sin constness del operador de una. Así que tenga cuidado aquí incluso si el estándar especifica el comportamiento correcto.

pensé MSVS sólo la búsqueda de constructor adecuado antes de operador de conversiones, pero luego encontramos que comienza a llamar de un operador B() si se quita la palabra const desde el constructor de B. Probablemente tiene un comportamiento especial para los temporales, debido a que el siguiente código todavía llama al constructor de B:

A a; 

B b = a; 
Cuestiones relacionadas