Conversor de conversión frente a operador de conversión: precedencia

Leer 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”. Considera el siguiente código:

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; } 

El código anterior muestra el “operador de conversión llamado A”, lo que significa que se llama al operador de conversión en lugar del constructor. Si elimina / comenta el código del operator B() de A , el comstackdor cambiará felizmente a usar el constructor (sin ningún otro cambio en el código).

Mis preguntas son:

  1. Dado que el comstackdor no considera B b = A(); para ser una llamada ambigua, debe haber algún tipo de precedencia 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 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.

Gracias por adelantado

Copia la inicialización y las funciones candidatas que se consideran que realizan las conversiones en la secuencia de conversión son las funciones de conversión y los constructores de conversión. Estos son en tu caso

 B(const A&) operator B() 

Ahora, esa es la forma en que los declaras. 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 vincularse a un valor r. Entonces, otra regla dice que cuando tienes dos funciones viables cuyos parámetros son referencias, entonces el candidato que tenga la menor calificación const ganará. Es por eso que su función de conversión gana. Intente hacer que el operator B una función 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 hace que la función de conversión sea una función constante, entonces GCC elegirá el constructor (¿entonces GCC parece pensar que B tiene más negocios con eso?). Cambie al modo pedante ( -pedantic ) para que cause un diagnóstico.


Estándar, 8.5/14

De lo contrario (es decir, para los casos de inicialización de copia restantes), las secuencias de conversión definidas por el usuario que pueden convertirse del tipo fuente al tipo de destino o (cuando se usa una función de conversión) 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

La resolución de sobrecarga se usa para seleccionar la conversión definida por el usuario que se invocará. Suponiendo que “cv1 T” es el tipo de objeto que se está inicializando, con T 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 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

  • La secuencia de conversión estándar S1 es una secuencia de conversión mejor que 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 cv de nivel superior -calificadores, y el tipo al que se refiere la referencia inicializada por S2 está más calificado como cv que el tipo al que se refiere la referencia inicializada por S1.

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

Pensé que MSVS solo buscaba un constructor adecuado antes del operador de conversiones, pero luego descubrí que comienza a llamar al operador B () de A si elimina la palabra const del constructor de B. Probablemente tenga algún comportamiento especial para los temporales, porque el siguiente código todavía llama al constructor de B:

 A a; B b = a;