Domanda

Perché il compilatore di Visual C++ chiamata sbagliata sovraccarico qui?

Io sono una sottoclasse di ostream che uso per definire un buffer per la formattazione.A volte ho voglia di creare una temporanea e, immediatamente, inserire una stringa con il solito << operatore di simile a questo:

M2Stream() << "the string";

Purtroppo, il programma chiama l'operatore<<(ostream, void *) membro di sovraccarico, invece dell'operatore<<(ostream, const char *) membro di uno.

Ho scritto l'esempio riportato di seguito come un test dove posso definire la mia M2Stream classe che riproduce il problema.

Penso che il problema è che il M2Stream() espressione produce un temporaneo e questo in qualche modo fa sì che il compilatore preferisce il vuoto * sovraccarico.Ma perché?Questo è confermato dal fatto che se devo fare il primo argomento per non membro sovraccarico const M2Stream &, ho un'ambiguità.

Un'altra cosa strana è che si chiama il desiderato const char * sovraccarico se io prima di definire una variabile di tipo const char * e quindi chiamare, invece di un valore letterale stringa di tipo char, come questo:

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

È come se il valore letterale di stringa ha un tipo diverso da quello const char * variabile!Non dovrebbero essere la stessa cosa?E perché fa il compilatore a causa di una chiamata per il vuoto * sovraccarico quando uso temporaneo e il valore letterale di stringa di tipo char?

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

Output:

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
È stato utile?

Soluzione

Il compilatore di fare la cosa giusta: Stream() << "hello"; dovrebbe usare il operator<< definita come una funzione membro.Perché il corso d'acqua temporaneo, oggetto non può essere associato a un non-const punto di riferimento, ma solo per un const riferimento, il non-membro dell'operatore che gestisce char const* non saranno selezionati.

E ' stato progettato in quel modo, come si vede quando si cambia operatore.Si ottiene ambiguità, perché il compilatore non può decidere quale degli operatori disponibili per l'uso.Perché tutti loro sono stati progettati con il rifiuto della non-membro operator<< in mente per provvisori.

Quindi, sì, un valore letterale stringa ha un tipo diverso rispetto a un char const*.Una stringa è un array di caratteri const.Ma questo non importa, nel tuo caso, penso.Non so cosa sovraccarichi di operator<< MSVC++ aggiunge.È consentita l'aggiunta di ulteriori sovraccarichi, purché non influisce sul comportamento di programmi validi.

Perché M2Stream() << s; funziona anche quando il primo parametro è un non-const reference...Beh, MSVC++ è un'estensione che permette di non-const riferimenti associare provvisori.Mettere al livello di attenzione di livello 4 per vedere un avviso di che (qualcosa come "estensione non standard utilizzato...").

Ora, perché non c'è un membro operatore<< che prende un void const*, e un char const* può convertire a che, l'operatore sarà scelto e l'indirizzo di output come la void const* il sovraccarico è per.

Ho visto nel codice che in realtà hanno un void* sovraccarico, non un void const* sovraccarico.Bene, un valore letterale stringa può convertire char*, anche se il tipo di un valore letterale stringa char const[N] (con N = numero di caratteri hai messo).Ma che la conversione è deprecato.Non dovrebbero essere standard che una stringa letterale converte void*.A me sembra che è un'altra estensione da MSVC++ compiler.Ma che spiegherebbe perché il valore letterale di stringa viene trattata in modo diverso rispetto al char const* puntatore.Questo è ciò che la Norma dice:

Una stringa letterale (2.13.4) che non è una stringa a livello letterale, può essere convertito in un rvalue di tipo "puntatore a char";una vasta stringa letterale, può essere convertito in un rvalue di tipo "puntatore a wchar_t".In entrambi i casi, il risultato è un puntatore al primo elemento dell'array.Questa conversione viene presa in considerazione solo quando vi è un'esplicita appropriato puntatore tipo di destinazione, e non quando vi è un generale bisogno di convertire da un lvalue per un rvalue.[Nota:questa conversione è deprecato.Si Veda L'Allegato D.]

Altri suggerimenti

Il primo problema è causato da strane e complicate regole del linguaggio C ++:

  1. Un temporaneo creato da una chiamata a un costruttore è un valore .
  2. Un valore non può essere associato a un riferimento non const.
  3. Tuttavia, un oggetto rvalue può avere metodi non const invocati su di esso.

Ciò che sta accadendo è che ostream& operator<<(ostream&, const char*), una funzione non membro, tenta di associare il M2Stream temporaneo creato a un riferimento non const, ma ciò non riesce (regola # 2); ma ostream& ostream::operator<<(void*) è una funzione membro e quindi può legarsi ad essa. In assenza della funzione const char*, viene selezionato come il miglior sovraccarico.

Non sono sicuro del motivo per cui i progettisti della libreria IOStreams abbiano deciso di rendere operator<<() per void* un metodo ma non ostream per <=>, ma è così, quindi abbiamo queste strane incoerenze con gestire.

Non sono sicuro del motivo per cui si sta verificando il secondo problema. Hai lo stesso comportamento su diversi compilatori? È possibile che sia un compilatore o un bug della libreria standard C ++, ma lo lascerei come scusa dell'ultima risorsa - almeno vedo se riesci a replicare il comportamento con un <=> normale prima.

Il problema è che stai usando un oggetto stream temporaneo. Modifica il codice nel modo seguente e funzionerà:

M2Stream ms;
ms << "the string";

Fondamentalmente, il compilatore si rifiuta di associare il temporaneo al riferimento non const.

Riguardo al tuo secondo punto sul perché si lega quando hai un " const char * " oggetto, questo credo sia un bug nel compilatore VC. Non posso dire con certezza, tuttavia, quando hai solo la stringa letterale, c'è una conversione in 'void *' e una conversione in 'const char *'. Quando hai l'oggetto 'const char *', allora non c'è nessuna conversione richiesta sul secondo argomento - e questo potrebbe essere un fattore scatenante per il comportamento non standard di VC per consentire l'associazione non const ref.

Credo che 8.5.3 / 5 sia la sezione dello standard che copre questo.

Non sono sicuro che il tuo codice debba essere compilato. Penso:

M2Stream & operator<<( void *vp )

dovrebbe essere:

M2Stream & operator<<( const void *vp )

In effetti, guardando più al codice, credo che tutti i tuoi problemi siano dovuti a const. Il seguente codice funziona come previsto:

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

È possibile utilizzare un sovraccarico come questo:

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

Come bonus aggiuntivo, ora conosci N come lunghezza dell'array.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top