Frage

Warum ruft der visuelle C ++ - Compiler hier die falsche Überladung auf?

Ich habe eine Unterklasse Ostream, mit der ich einen Puffer für die Formatierung definiere. Manchmal möchte ich einen temporären erstellen und sofort eine Zeichenfolge mit dem üblichen << Operator wie folgt einfügen:

M2Stream() << "the string";

Leider ruft das Programm den Bediener << (Ostream, void *) -Member -Überladung anstelle des Operators << (OSTREAM, const char *) Nicht -Mitglied auf.

Ich habe das Beispiel unten als Test geschrieben, bei dem ich meine eigene M2stream -Klasse definiere, die das Problem reproduziert.

Ich denke, das Problem ist, dass der Ausdruck von M2stream () eine temporäre Erzeugung erzeugt und dass der Compiler irgendwie die Hohlraumüberlastung bevorzugt. Aber wieso? Dies wird durch die Tatsache ausgelöst, dass ich, wenn ich das erste Argument für die Nichtmitglied -Überlastung konstant m2stream & mache, eine Unklarheit bekomme.

Eine andere seltsame Sache ist, dass es die gewünschte Const char * überlastet, wenn ich zuerst eine Variable des Typs const char * definiere und sie dann anstelle einer wörtlichen Zeichenfolge wie folgt nenne:

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

Es ist, als hätte die buchstäbliche Zeichenfolge einen anderen Typ als die const char * Variable! Sollten sie nicht gleich sein? Und warum verursacht der Compiler einen Anruf auf die Hohlraumüberlastung, wenn ich die temporäre und die buchstäbliche Zeichenfolge verwende?

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

Ausgabe:

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
War es hilfreich?

Lösung

Der Compiler macht das Richtige: Stream() << "hello"; sollte das verwenden operator<< als Mitgliedsfunktion definiert. Da das temporäre Stream-Objekt nicht an eine nicht konstante Referenz gebunden werden kann, sondern nur an eine CONT-Referenz, dem Nichtmitgliedbetreiber, der übernimmt char const* wird nicht ausgewählt.

Und es ist so gestaltet, wie Sie sehen, wenn Sie diesen Bediener ändern. Sie erhalten Unklarheiten, da der Compiler nicht entscheiden kann, welches der verfügbaren Betreiber verwendet werden soll. Weil alle mit Ablehnung des Nichtmitglieds entworfen wurden operator<< Angesichts der Tempary.

Dann, ja, ein String -Literal hat einen anderen Typ als a char const*. Ein String -Literal ist eine Reihe von Const -Zeichen. Aber das würde in Ihrem Fall keine Rolle spielen, denke ich. Ich weiß nicht, was Überladungen von operator<< MSVC ++ fügt hinzu. Es darf weitere Überladungen hinzufügen, solange sie das Verhalten gültiger Programme nicht beeinflussen.

Warum M2Stream() << s; Funktioniert auch, wenn der erste Parameter eine nicht konstante Referenz ist ... Nun, MSVC ++ hat eine Erweiterung, die es ermöglicht, nicht konstante Referenzen an Zeiträume zu binden. Setzen Sie die Warnstufe auf Level 4 ein, um eine Warnung darüber zu sehen (so etwas wie "Nicht standardmäßige Erweiterung ...").

Nun, weil es einen Mitgliedsbetreiber gibt <<, der a dauert void const*, und ein char const* kann darauf konvertieren, dass der Bediener ausgewählt wird und die Adresse ausgeben wird, da dies der ist, was der ist void const* Überlastung ist für.

Ich habe in Ihrem Code gesehen, dass Sie tatsächlich eine haben void* Überlastung, nicht a void const* Überlast. Nun, ein Streichliteral kann zu konvertieren char*, obwohl der Typ eines String -Literales ist char const[N] (Da n die Menge an Charakteren ist, die Sie aufgenommen haben). Aber diese Umwandlung ist veraltet. Es sollte nicht Standard sein, dass ein String buchstäblich zu konvertiert void*. Für mich ist es eine weitere Erweiterung des MSVC ++ - Compiler. Aber das würde erklären, warum das Streichliteral anders behandelt wird als die char const* Zeiger. Dies ist, was der Standard sagt:

Eine Saitenliteral (2.13.4), die kein breites Saitenliteral ist, kann in einen RValue vom Typ "Zeiger auf Zeichen" umgewandelt werden; Ein breites Saitenliteral kann in einen Typ "Zeiger auf wchar_t" umgewandelt werden. In beiden Fällen ist das Ergebnis ein Zeiger auf das erste Element des Arrays. Diese Konvertierung wird nur dann berücksichtigt, wenn ein explizit geeigneter Zeigerzieltyp vorliegt und nicht, wenn allgemein ein LVALUE in ein RValue konvertiert wird. [Hinweis: Diese Konvertierung ist veraltet. Siehe Anhang D.

Andere Tipps

Das erste Problem wird durch seltsame und knifflige C ++ - Sprachregeln verursacht:

  1. Ein durch einen Aufruf eines Konstruktors erstellter Temporärer ist ein rvalue.
  2. Ein RValue darf nicht an eine nicht konstante Referenz gebunden sein.
  3. Ein RValue-Objekt kann jedoch nicht konstante Methoden aufnehmen.

Was passiert ist, ist das ostream& operator<<(ostream&, const char*), eine Nichtmitgliedfunktion, Versuche, die zu binden M2Stream Temporär Sie erstellen eine nicht konstante Referenz, aber das schlägt fehl (Regel 2); aber ostream& ostream::operator<<(void*) ist eine Mitgliedsfunktion und kann daher daran binden. In Abwesenheit der const char* Funktion, es wird als beste Überlast ausgewählt.

Ich bin mir nicht sicher, warum sich die Designer der iOstreams -Bibliothek entschieden haben, zu machen operator<<() zum void* eine Methode, aber nicht operator<<() zum const char*, Aber so ist es, also haben wir diese seltsamen Inkonsistenzen, mit denen wir uns befassen können.

Ich bin mir nicht sicher, warum das zweite Problem auftritt. Erhalten Sie das gleiche Verhalten über verschiedene Compiler hinweg? Es ist möglich, dass es sich um einen Compiler- oder C ++ - Standard -Bibliotheksfehler handelt, aber ich würde dies als Ausrede des letzten Auswegs verlassen - zumindest, ob Sie das Verhalten mit einem normalen Refitieren können ostream Erste.

Das Problem ist, dass Sie ein temporäres Stream -Objekt verwenden. Ändern Sie den Code in Folgendes und er funktioniert:

M2Stream ms;
ms << "the string";

Grundsätzlich weigert sich der Compiler, die temporäre an die Nicht -Konst -Referenz zu binden.

In Bezug auf Ihren zweiten Punkt darüber, warum es bindet, wenn Sie ein "const char *" Objekt haben, ist dies meiner Meinung nach ein Fehler im VC -Compiler. Ich kann jedoch nicht mit Sicherheit sagen, wenn Sie nur die Streicherliteral haben, gibt es eine Konvertierung in 'void *' und eine Umwandlung in 'const char *'. Wenn Sie das 'const char *' Objekt haben, ist für das zweite Argument keine Konvertierung erforderlich - und dies kann ein Auslöser für das nicht standardmäßige Verhalten von VC sein, um die Nicht -Konstant -Ref -Bindung zuzulassen.

Ich glaube, 8.5.3/5 ist der Abschnitt des Standards, der dies abdeckt.

Ich bin mir nicht sicher, ob Ihr Code kompiliert wird. Ich finde:

M2Stream & operator<<( void *vp )

sollte sein:

M2Stream & operator<<( const void *vp )

In der Tat, wenn ich den Code mehr betrachte, glaube ich, dass alle Ihre Probleme bis zu Konstant sind. Der folgende Code funktioniert wie erwartet:

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

Sie könnten eine Überladung wie diese verwenden:

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

Als zusätzlichen Bonus wissen Sie jetzt, dass n die Länge des Arrays ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top