Question

Pourquoi le compilateur Visual C ++ appelle-t-il la mauvaise surcharge ici?

Je dispose d’une sous-classe d’ostream que j’utilise pour définir un tampon de formatage. Parfois, je veux créer un temporaire et y insérer immédiatement une chaîne avec l'habituel & Lt; & Lt; opérateur comme ceci:

M2Stream() << "the string";

Malheureusement, le programme appelle l'opérateur < < (ostream, void *), membre surchargé, au lieu de l'opérateur < < (ostream, const char *) non membre un.

J'ai écrit l'exemple ci-dessous comme test dans lequel je définis ma propre classe M2Stream qui reproduit le problème.

Je pense que le problème est que l'expression M2Stream () produit un temporaire, ce qui pousse le compilateur à préférer la surcharge void *. Mais pourquoi? Ceci est corroboré par le fait que si je fais le premier argument pour la surcharge non membre Const M2Stream &, J'obtiens une ambiguïté.

Une autre chose étrange est qu’elle appelle le surchargement * désiré si je définis d’abord une variable de type const char * puis que je l’appelle au lieu d’une chaîne de caractères littérale, comme ceci:

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

C'est comme si la chaîne littérale avait un type différent de celui de la variable const char *! Ne devraient-ils pas être les mêmes? Et pourquoi le compilateur provoque-t-il un appel à la surcharge void * lorsque j'utilise les chaînes de caractères temporaire et littérale?

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

Sortie:

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
Était-ce utile?

La solution

Le compilateur fait le bon choix: Stream() << "hello"; doit utiliser la operator<< définie en tant que fonction membre. Étant donné que l'objet de flux temporaire ne peut pas être lié à une référence non const mais uniquement à une référence const, l'opérateur non membre qui gère char const* ne sera pas sélectionné.

Et il est conçu de cette façon, comme vous le voyez lorsque vous changez d'opérateur. Vous obtenez des ambiguïtés, car le compilateur ne peut pas décider quel opérateur utiliser. Parce que tous ont été conçus avec le rejet du non-membre M2Stream() << s; en tête pour les temporaires.

Ensuite, oui, un littéral de chaîne a un type différent de void const*. Un littéral est un tableau de caractères const. Mais cela n'aurait pas d'importance dans votre cas, je pense. Je ne sais pas ce que surcharges de void* MSVC ++ ajoute. Il est permis d'ajouter des surcharges supplémentaires, tant qu'elles n'affectent pas le comportement des programmes valides.

Pourquoi char* fonctionne même lorsque le premier paramètre est une référence non-const ... Eh bien, MSVC ++ a une extension qui autorise les références non-const à se lier à des entités temporaires. Mettez le niveau d'alerte au niveau 4 pour en voir un avertissement (quelque chose comme & "; Extension non standard utilisée ... &";).

Maintenant, car il y a un opérateur membre < < cela prend un char const[N], et un <=> peut y être converti, cet opérateur sera choisi et l'adresse sera générée sous la <>> surcharge utilisée.

J'ai vu dans votre code que vous avez réellement une surcharge <=> et non pas <=>. Un littéral de chaîne peut être converti en <=>, même si le type d'un littéral de chaîne est <=> (N étant le nombre de caractères que vous avez saisis). Mais cette conversion est obsolète. Il ne devrait pas être normal qu'un littéral de chaîne se convertisse en <=>. Il me semble que c'est une autre extension du compilateur MSVC ++. Mais cela expliquerait pourquoi le littéral chaîne est traité différemment du pointeur <=>. Voici ce que dit la norme:

  

Un littéral de chaîne (2.13.4) qui n'est pas un littéral de chaîne large peut être converti en une valeur rvalue de type & "; pointeur sur char &" ;; un littéral de chaîne large peut être converti en une valeur rvalue de type & "; pointeur sur wchar_t &"; Dans les deux cas, le résultat est un pointeur sur le premier élément du tableau. Cette conversion est considérée uniquement lorsqu'il existe un type de cible de pointeur approprié explicite, et non lorsqu'il est généralement nécessaire de convertir une lvalue en une rvalue. [Remarque: cette conversion est obsolète. Voir l'annexe D.]

Autres conseils

Le premier problème est causé par des règles de langage C ++ étranges et difficiles:

  1. Un temporaire créé par un appel à un constructeur est une rvalue .
  2. Une valeur rvalue ne peut pas être liée à une référence non-const.
  3. Cependant, un objet rvalue peut avoir des méthodes non const invoquées.

Ce qui se passe, c’est que ostream& operator<<(ostream&, const char*), une fonction non membre, tente de lier le M2Stream temporaire que vous créez à une référence non const, mais cela échoue (règle n ° 2); mais ostream& ostream::operator<<(void*) est une fonction membre et peut donc s'y lier. En l’absence de la const char* fonction, elle est sélectionnée comme meilleure surcharge.

Je ne suis pas sûr de savoir pourquoi les concepteurs de la bibliothèque IOStreams ont décidé de créer operator<<() pour void* une méthode mais pas ostream pour <=>, mais c'est comme ça. Nous avons donc ces incohérences étranges traiter avec.

Je ne sais pas pourquoi le deuxième problème se pose. Avez-vous le même comportement sur différents compilateurs? Il est possible qu’il s’agisse d’un bogue du compilateur ou de la bibliothèque standard C ++, mais je laisserais cela comme excuse de dernier recours - au moins, voyez si vous pouvez reproduire le comportement avec un <=> d’abord.

.

Le problème est que vous utilisez un objet de flux temporaire. Modifiez le code comme suit et cela fonctionnera:

M2Stream ms;
ms << "the string";

En gros, le compilateur refuse de lier le temporaire à la référence non const.

Concernant votre deuxième point sur la raison pour laquelle il est lié lorsque vous avez un & "const char * &"; objet, cela, je crois, est un bug dans le compilateur VC. Je ne peux pas dire avec certitude, cependant, lorsque vous avez juste le littéral chaîne, il y a une conversion en 'void *' et une conversion en 'const char *'. Lorsque vous avez l'objet 'const char *', aucune conversion n'est requise sur le deuxième argument. Cela pourrait être un déclencheur pour le comportement non standard de VC autorisant la liaison non const de référence.

Je crois que 8.5.3 / 5 est la partie de la norme qui couvre cela.

Je ne suis pas sûr que votre code devrait compiler. Je pense:

M2Stream & operator<<( void *vp )

devrait être:

M2Stream & operator<<( const void *vp )

En fait, en regardant davantage le code, je pense que tous vos problèmes sont dus à const. Le code suivant fonctionne comme prévu:

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

Vous pouvez utiliser une surcharge telle que celle-ci:

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

En prime, vous savez maintenant que N est la longueur du tableau.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top