Pregunta

¿Por qué el compilador de Visual C ++ llama a la sobrecarga incorrecta aquí?

Tengo una subclase de ostream que utilizo para definir un búfer para formatear. A veces quiero crear un temporal e inmediatamente insertar una cadena en él con el habitual & Lt; & Lt; operador como este:

M2Stream() << "the string";

Desafortunadamente, el programa llama al operador < < (ostream, void *) sobrecarga de miembros, en lugar de al operador < < (ostream, const char *) no miembro

Escribí el ejemplo a continuación como una prueba en la que defino mi propia clase M2Stream que reproduce el problema.

Creo que el problema es que la expresión M2Stream () produce una temporal y esto de alguna manera hace que el compilador prefiera la sobrecarga void *. ¿Pero por qué? Esto se confirma por el hecho de que si hago el primer argumento para la sobrecarga de no miembros const M2Stream & Amp ;, obtengo una ambigüedad.

Otra cosa extraña es que llama a la sobrecarga deseada de const char * si primero defino una variable de tipo const char * y luego la llamo, en lugar de una cadena de caracteres literal, como esta:

const char *s = "char string variable";
M2Stream() << s;  

¡Es como si la cadena literal tuviera un tipo diferente que la variable const char *! ¿No deberían ser lo mismo? ¿Y por qué el compilador causa una llamada a la sobrecarga void * cuando uso la cadena de caracteres temporal y literal?

#include "stdafx.h"
#include <iostream>
using namespace std;


class M2Stream
{
public:
    M2Stream &operator<<(void *vp)
    {
        cout << "M2Stream bad operator<<(void *) called with " << (const char *) vp << endl;
        return *this;
    }
};

/* If I make first arg const M2Stream &os, I get
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(39) : error C2666: 'M2Stream::operator <<' : 2 overloads have similar conversions
        \tests\t_stream_insertion_op\t_stream_insertion_op.cpp(13): could be 'M2Stream &M2Stream::operator <<(void *)'
        \tests\t_stream_insertion_op\t_stream_insertion_op.cpp(20): or 'const M2Stream &operator <<(const M2Stream &,const char *)'
        while trying to match the argument list '(M2Stream, const char [45])'
        note: qualification adjustment (const/volatile) may be causing the ambiguity
*/
const M2Stream & operator<<(M2Stream &os, const char *val)
{
    cout << "M2Stream good operator<<(const char *) called with " << val << endl;
    return os;
}


int main(int argc, char argv[])
{
    // This line calls void * overload, outputs: M2Stream bad operator<<(void *) called with literal char string on constructed temporary
    M2Stream() << "literal char string on constructed temporary";

    const char *s = "char string variable";

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
    M2Stream() << s;  

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
    M2Stream m;
    m << "literal char string on prebuilt object";
    return 0;
}

Salida:

M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream good operator<<(const char *) called with char string variable
M2Stream good operator<<(const char *) called with literal char string on prebuilt object
¿Fue útil?

Solución

El compilador está haciendo lo correcto: Stream() << "hello"; debería usar el operator<< definido como una función miembro. Debido a que el objeto de flujo temporal no puede vincularse a una referencia no constante sino solo a una referencia constante, no se seleccionará el operador no miembro que maneja char const*.

Y está diseñado de esa manera, como puede ver cuando cambia ese operador. Obtienes ambigüedades, porque el compilador no puede decidir cuál de los operadores disponibles usar. Porque todos ellos fueron diseñados con el rechazo de los no miembros M2Stream() << s; en mente para los temporales.

Entonces, sí, un literal de cadena tiene un tipo diferente que un void const*. Un literal de cadena es una matriz de caracteres constantes. Pero eso no importaría en tu caso, creo. No sé qué sobrecargas de void* MSVC ++ agrega. Está permitido agregar más sobrecargas, siempre que no afecten el comportamiento de los programas válidos.

Por qué char* funciona incluso cuando el primer parámetro es una referencia no constante ... Bueno, MSVC ++ tiene una extensión que permite que las referencias no constantes se unan a temporales. Coloque el nivel de advertencia en el nivel 4 para ver una advertencia al respecto (algo así como & Quot; extensión no estándar utilizada ... & Quot;).

Ahora, porque hay un operador miembro < < que toma un char const[N], y un <=> puede convertir a eso, ese operador será elegido y la dirección se generará, ya que para eso es la sobrecarga <=>.

He visto en su código que realmente tiene una sobrecarga <=>, no una sobrecarga <=>. Bueno, un literal de cadena se puede convertir a <=>, a pesar de que el tipo de literal de cadena es <=> (donde N es la cantidad de caracteres que pone). Pero esa conversión está en desuso. No debería ser estándar que un literal de cadena se convierta a <=>. Me parece que es otra extensión del compilador MSVC ++. Pero eso explicaría por qué el literal de cadena se trata de manera diferente que el puntero <=>. Esto es lo que dice el Estándar:

  

Un literal de cadena (2.13.4) que no es un literal de cadena ancha se puede convertir a un valor de tipo " puntero a char " ;; un literal de cadena ancha se puede convertir a un valor r de tipo " puntero a wchar_t " ;. En cualquier caso, el resultado es un puntero al primer elemento de la matriz. Esta conversión se considera solo cuando hay un tipo de objetivo de puntero explícito apropiado, y no cuando hay una necesidad general de convertir de un valor l a un valor r. [Nota: esta conversión está en desuso. Ver Anexo D.]

Otros consejos

El primer problema es causado por reglas de lenguaje C ++ extrañas y difíciles:

  1. Un temporal creado por una llamada a un constructor es un rvalue .
  2. Un valor r no puede estar vinculado a una referencia no constante.
  3. Sin embargo, un objeto rvalue puede tener métodos no constantes invocados en él.

Lo que está sucediendo es que ostream& operator<<(ostream&, const char*), una función no miembro, intenta vincular el M2Stream temporal que crea a una referencia no constante, pero eso falla (regla # 2); pero ostream& ostream::operator<<(void*) es una función miembro y, por lo tanto, puede vincularse a ella. En ausencia de la función const char*, se selecciona como la mejor sobrecarga.

No estoy seguro de por qué los diseñadores de la biblioteca IOStreams decidieron hacer operator<<() para void* un método pero no ostream para <=>, pero así es como es, así que tenemos estas extrañas inconsistencias para tratar con.

No estoy seguro de por qué está ocurriendo el segundo problema. ¿Obtiene el mismo comportamiento en diferentes compiladores? Es posible que sea un compilador o un error de la Biblioteca estándar de C ++, pero lo dejaría como excusa como último recurso; al menos vea si puede replicar el comportamiento con un <=> primero.

El problema es que está utilizando un objeto de flujo temporal. Cambie el código a lo siguiente y funcionará:

M2Stream ms;
ms << "the string";

Básicamente, el compilador se niega a vincular lo temporal a la referencia no constante.

Con respecto a su segundo punto sobre por qué se une cuando tiene un " const char * " objeto, esto creo que es un error en el compilador de VC. Sin embargo, no puedo decir con certeza, cuando solo tiene el literal de cadena, hay una conversión a 'void *' y una conversión a 'const char *'. Cuando tiene el objeto 'const char *', entonces no se requiere conversión en el segundo argumento, y esto podría ser un desencadenante del comportamiento no estándar de VC para permitir el enlace de referencia no const.

Creo que 8.5.3 / 5 es la sección del estándar que cubre esto.

No estoy seguro de que su código deba compilarse. Yo pienso:

M2Stream & operator<<( void *vp )

debería ser:

M2Stream & operator<<( const void *vp )

De hecho, mirando más el código, creo que todos sus problemas se deben a constantes. El siguiente código funciona como se esperaba:

#include <iostream>
using namespace std;


class M2Stream
{
};

const M2Stream & operator<<( const M2Stream &os, const char *val)
{
    cout << "M2Stream good operator<<(const char *) called with " << val << endl;
    return os;
}


int main(int argc, char argv[])
{
    M2Stream() << "literal char string on constructed temporary";

    const char *s = "char string variable";

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
    M2Stream() << s;  

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
    M2Stream m;
    m << "literal char string on prebuilt object";
    return 0;
}

Podría usar una sobrecarga como esta:

template <int N>
M2Stream & operator<<(M2Stream & m, char const (& param)[N])
{
     // output param
     return m;
}

Como una ventaja adicional, ahora sabes que N es la longitud de la matriz.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top