programacion poo polimorfismo orientada objetos metodos herencia ejemplos ejemplo constructores codigo c++ c++14 language-lawyer c++17

poo - Llame al operador de conversión en lugar de convertir el constructor en c++ 17 durante la resolución de sobrecarga



polimorfismo c++ (1)

Considere las siguientes dos clases:

#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << ''/n'') struct D; struct C { C() { PRETTY(this);} C(const C&) { PRETTY(this);} C(const D&) { PRETTY(this);} }; struct D { D() { PRETTY(this);} operator C() { PRETTY(this); return C();} };

Nos interesa la resolución de sobrecarga entre los dos constructores:

C::C(const C&); C::C(const D&);

Este código funciona como se espera:

void f(const C& c){ PRETTY(&c);} void f(const D& d){ PRETTY(&d);} /*--------*/ D d; f(d); //calls void f(const D& d)

ya que void f(const D& d) es una mejor coincidencia.

Pero :

D d; C c(d);

(como se puede ver here )

llama a D::operator C() cuando compila con std=c++17 , y llama a C::C(const D&) con std=c++14 en clang y gcc HEAD. Además, este comportamiento ha cambiado recientemente: con clang 5, se llama a C::C(const D&) con std=c++17 y std=c++14 .

¿Cuál es el comportamiento correcto aquí?

Lectura provisional de la norma (último borrador N4687):

C c(d) es una inicialización directa que no es una elision de copia ([dcl.init] /17.6.1). [dcl.init] /17.6.2 nos dice que los constructores correspondientes se enumeran y que se elige el mejor por resolución de sobrecarga. [over.match.ctor] nos dice que los constructores aplicables son en este caso todos los constructores.

En este caso: C() , C(const C&) y C(const D&) (no mover ctor). C() claramente no es viable y, por lo tanto, se descarta del conjunto de sobrecarga. ([over.match.viable])

Los constructores no tienen un parámetro de objeto implícito, por lo que C(const C&) y C(const D&) toman exactamente un parámetro. ([over.match.funcs] / 2)

Ahora vamos a [over.match.best]. Aquí encontramos que necesitamos determinar cuál de estas dos secuencias de conversión implícitas (ICS) es mejor. El ICS de C(const D&) solo involucra una secuencia de conversión estándar, pero el ICS de C(const C&) involucra una secuencia de conversión definida por el usuario.

Por lo tanto, C(const D&) debe seleccionarse en lugar de C(const C&) .

Curiosamente, estas dos modificaciones hacen que se llame al constructor "correcto":

operator C() { /* */ } en el operator C() const { /* */ }

o

C(const D&) { /* */ } en C(D&) { /* */ }

Esto es lo que sucedería (creo) en el caso de inicialización de copia, donde las conversiones definidas por el usuario y los constructores de conversión están sujetos a una resolución de sobrecarga.

Como Columbo recomienda, presenté un informe de error con gcc y clang

error gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82840

error de Clang https://bugs.llvm.org/show_bug.cgi?id=35207


Cuestión central 243 (17 años de edad!):

Hay un problema moderadamente serio con la definición de resolución de sobrecarga. Considera este ejemplo:

struct B; struct A { A(B); }; struct B { operator A(); } b; int main() { (void)A(b); }

Esta es más o menos la definición de "ambiguo", ¿verdad? Desea convertir una B en una A , y hay dos formas igualmente buenas de hacerlo: un constructor de A que toma una B y una función de conversión de B que devuelve una A

Lo que descubrimos cuando rastreamos esto a través del estándar, desafortunadamente, es que el constructor está favorecido sobre la función de conversión. La definición de inicialización directa (la forma entre paréntesis) de una clase considera solo a los constructores de esa clase. En este caso, los constructores son el constructor A(B) y el constructor de copia A(const A&) generado implícitamente A(const A&) . Aquí está cómo se clasifican en el argumento de coincidencia:

  • A(B) : coincidencia exacta (necesita una B , tiene una B )
  • A(const A&) : conversión definida por el usuario ( B::operator A utilizado para convertir B en A )

En otras palabras, la función de conversión se considera, pero está operando con, en efecto, una desventaja de una conversión definida por el usuario. Para decirlo de otra manera, este problema es un problema de ponderación, no un problema que ciertas rutas de conversión no se consideran. […]

Notas de la reunión del 10/01:

Resulta que existe una práctica en ambos sentidos en este tema, por lo que no está claro si está "roto". Hay alguna razón para sentir que algo que se parece a una "llamada del constructor" debería llamar a un constructor si es posible, en lugar de una función de conversión. El CWG decidió dejarlo solo.

Parece que tienes razón. Además, Clang y GCC que seleccionan el operador de conversión no son la mejor opción, ni de acuerdo con la redacción ni la intuición, por lo que, a menos que esto se deba a la compatibilidad con versiones anteriores (e incluso entonces), sería apropiado un informe de error.