2010-04-10 7 views
2

Je rencontre une énigme épineuse dans mon code de base. Je ne peux pas vraiment dire pourquoi mon code génère cette erreur, mais (par exemple) std :: string ne le fait pas.L'ambiguïté de surcharge d'addition C++

class String { 
public: 
    String(const char*str); 
    friend String operator+ (const String& lval, const char *rval); 
    friend String operator+ (const char *lval, const String& rval); 
    String operator+ (const String& rval); 
}; 

La mise en œuvre de ceux-ci est assez facile à imaginer par vous-même.

Mon programme pilote contient les éléments suivants:

String result, lval("left side "), rval("of string"); 
char lv[] = "right side ", rv[] = "of string"; 
result = lv + rval; 
printf(result); 
result = (lval + rv); 
printf(result); 

qui génère l'erreur suivante dans gcc 4.1.2:

driver.cpp:25: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: 
String.h:22: note: candidate 1: String operator+(const String&, const char*) 
String.h:24: note: candidate 2: String String::operator+(const String&) 

So far so good, non? Malheureusement, mon constructeur String (const char * str) est si pratique à avoir en tant que constructeur implicite, que l'utilisation du mot-clé explicite pour résoudre cela provoquerait simplement une pile différente de problèmes.

De plus ... std :: string n'a pas à recourir à cela, et je n'arrive pas à comprendre pourquoi. Par exemple, dans basic_string.h, ils sont déclarés comme suit:

template<typename _CharT, typename _Traits, typename _Alloc> 
basic_string<_CharT, _Traits, _Alloc> 
operator+(const basic_string<_CharT, _Traits, _Alloc>& __lhs, 
      const basic_string<_CharT, _Traits, _Alloc>& __rhs) 

template<typename _CharT, typename _Traits, typename _Alloc> 
basic_string<_CharT,_Traits,_Alloc> 
operator+(const _CharT* __lhs, 
      const basic_string<_CharT,_Traits,_Alloc>& __rhs); 

et ainsi de suite. Le constructeur basic_string n'est pas déclaré explicite. Comment cela ne provoque pas la même erreur que moi, et comment puis-je obtenir le même comportement?

Répondre

9

La raison de l'ambiguïté est qu'une fonction candidate est meilleure qu'une autre fonction candidate seulement si aucun de ses paramètres sont une plus mauvaise correspondance que les paramètres de l'autre. Tenez compte de vos deux fonctions:

friend String operator+(const String&, const char*); // (a) 
String operator+(const String&);      // (b) 

Vous appelez operator+ avec un String et un const char*.

Le deuxième argument, de type const char*, correspond clairement à (a) mieux qu'à (b). C'est une correspondance exacte pour (a), mais une conversion définie par l'utilisateur est requise pour (b). Par conséquent, pour qu'il y ait ambiguïté, le premier argument doit correspondre à (b) mieux qu'à (a). Le String sur le côté gauche de l'appel à operator+ n'est pas const. Par conséquent, il correspond (b), qui est une fonction membre non-const, mieux que (a), qui prend un const String&.

Par conséquent, l'une des solutions suivantes lèverait l'ambiguïté:

  • Modifier le membre operator+ être une fonction membre const
  • Modifier le non-membre operator+ de prendre une String& au lieu d'un const String&
  • Appel operator+ avec une chaîne const sur le côté gauche

Évidemment, le premier, also suggested by UncleBens, est le meilleur chemin à parcourir.

+0

Il ne m'a même pas traversé l'esprit que la constance serait cause d'un mauvais match. Vous et UncleBens étiez (bien sûr) exactement raison. Une autre solution consisterait en fait à faire du membre un non-membre qui accepte deux chaînes const - c'est en fait pourquoi la version std :: string n'a pas d'erreur. Bien sûr, cela équivaut à votre première suggestion, juste écrit différemment. – Nate

5

Il suffit dans ce cas juste pour définir le operator+:

String operator+(const String& lval, const String& rval); 

Parce que vous fournissez un constructeur de prendre un char*, un String peut être construit à partir d'un char* lors de l'appel à operator+. Par exemple:

String hello = "Hello, "; 
const char* world = "world!"; 

String helloWorld = hello + world; 

A String sera construit temporaire avec le contenu du char*world (parce que votre constructeur est pas explicite), puis seront transmis les deux objets String-operator+.

+0

Cela crée un objet temporaire inutile, peut-être à un coût considérable en temps et en mémoire. –

+0

@Daniel Newby: Pas vraiment, car le compilateur est capable d'éluder la copie assez souvent. De même que "coût considérable de temps et de mémoire" est exactement ce que "std :: string" fait, comme demandé par l'OP dans son message original. –

1

Vous avez montré que basic_string a des implémentations de operator+ correspondant aux deuxième et troisième opérateurs de votre classe String. Est-ce que basic_string a également un opérateur correspondant à votre premier opérateur [friend String operator+ (const String& lval, const char *rval);]? Que se passe-t-il si vous supprimez cet opérateur?

2

Les fonctions de modèle et hors modèle suivent des règles différentes. Les fonctions du modèle sont sélectionnées sur les types de paramètres réels, sans aucune conversion. Dans le cas du non-modèle (c'est-à-dire votre code), une conversion implicite peut être appliquée. Ainsi, le contenu du template dans basic_string n'est pas ambigu, mais le tien est.

3

L'erreur disparaît si vous déclarez le membre + const comme il se doit.

class String { 
public: 
    String(const char*str); 
    friend String operator+ (const String& lval, const char *rval); 
    friend String operator+ (const char *lval, const String& rval); 
    String operator+ (const String& rval) const; //<-- here 
}; 

Vous ne savez pas exactement pourquoi. Peut-être préfère-t-il si possible les arguments de liaison à la référence const, donc la première surcharge correspond mieux à la valeur de gauche et la troisième surcharge correspond mieux à la valeur de droite.

Better explanation. (Doit avoir mal interprété le problème un peu.)


printf(result); 

Ne me dites pas votre chaîne a la conversion implicite const char* ... C'est le mal.

+4

Espérons qu'il s'agisse d'une fonction printf non standard trouvée via ADL. Bien qu'il devrait être 'puts', pas' printf', si tout ce qu'il fait est imprimer l'argument. –

+0

'puts' ajoute une nouvelle ligne, cependant. Je suppose que ça devrait être 'printf ("% s ", str.c_str());' si le retour chariot n'est pas désiré. – UncleBens

+1

+1 C'est sans doute la meilleure option. J'ai essayé d'expliquer l'ambiguïté: http://stackoverflow.com/questions/2613645/c-addition-overload-ambiguity/2614085#2614085 –