2017-08-28 2 views
18

operator bool interrompt l'utilisation de operator< dans l'exemple suivant. Quelqu'un peut-il expliquer pourquoi bool est tout aussi pertinent dans l'expression if (a < 0) que l'opérateur spécifique, et s'il existe une solution de contournement?Pourquoi cette expression C++ impliquant des opérateurs surchargés et des conversions implicites est-elle ambiguë?

struct Foo { 
    Foo() {} 
    Foo(int x) {} 

    operator bool() const { return false; } 

    friend bool operator<(const Foo& a, const Foo& b) { 
     return true; 
    } 
}; 

int main() { 
    Foo a, b; 
    if (a < 0) { 
     a = 0; 
    } 
    return 1; 
} 

Quand je compile, je reçois:

g++ foo.cpp 
foo.cpp: In function 'int main()': 
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int') 
    if (a < 0) { 
     ^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in> 
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&) 
    friend bool operator<(const Foo& a, const Foo& b) 
+4

'0' est de type' int', pas de type 'Foo' – Rafalon

+11

Avez-vous vraiment besoin de ces deux conversions implicites? Les conversions implicites uniques peuvent causer des maux de tête, mais les conversions implicites à deux voies sont une véritable migraine. – TartanLlama

+8

Rendre le constructeur à argument unique 'explicite' –

Répondre

13

Les points importants sont les suivants:

D'abord, il y a deux pertinentes de operator < surcharges.

  • operator <(const Foo&, const Foo&). L'utilisation de cette surcharge nécessite une conversion définie par l'utilisateur du littéral 0 en Foo en utilisant Foo(int).
  • operator <(int, int). L'utilisation de cette surcharge nécessite de convertir Foo en bool avec le operator bool() défini par l'utilisateur, suivi d'un de promotion à int (c'est, en standardese, différent d'une conversion, comme cela a été souligné par Bo Persson).

La question ici est: D'où vient l'ambiguïté? Certes, le premier appel, qui ne nécessite qu'une conversion définie par l'utilisateur, est plus sensible que le second, ce qui nécessite une conversion définie par l'utilisateur suivie d'une promotion?

Mais ce n'est pas le cas. La norme attribue un rang à chaque candidat . Cependant, il n'y a pas de classement pour "conversion définie par l'utilisateur suivie d'une promotion". Cela a le même rang que l'utilisation d'une conversion définie par l'utilisateur. Tout simplement (mais officieusement) mettre, la séquence de classement ressemble un peu à ceci:

  1. correspondance exacte
  2. (seulement) la promotion nécessaire
  3. (uniquement) conversion implicite nécessaire (y compris les « dangereux » hérités de C tels que float à int)
  4. conversion définie par l'utilisateur requis

(non-responsabilité:. comme mentionné précédemment, c'est informel Il obtient beaucoup plus complexe lorsque mul il y a des arguments secondaires, et je n'ai pas non plus mentionné de références ou de qualification de cv. Ceci est juste une vue d'ensemble approximative.)

Cela explique, espérons-le, pourquoi l'appel est ambigu. Maintenant, pour la partie pratique de la façon de résoudre ce problème. Presque jamais quelqu'un qui fournit operator bool() veut qu'il soit implicitement utilisé dans les expressions impliquant l'arithmétique entière ou les comparaisons. En C++ 98, il y avait des solutions de contournement obscures, allant de std::basic_ios<CharT, Traits>::operator void * aux versions "améliorées" plus sûres impliquant des pointeurs vers des membres ou des types privés incomplets. Heureusement, C++ 11 introduit une manière plus lisible et cohérente d'empêcher la promotion d'entier après des utilisations implicites de operator bool(), qui est de marquer l'opérateur comme explicit. Cela supprimera entièrement la surcharge operator <(int, int), plutôt que de simplement la "rétrograder".

Comme d'autres l'ont mentionné, vous pouvez également marquer le constructeur Foo(int) comme explicite. Cela aura pour effet inverse de supprimer la surcharge operator <(const Foo&, const Foo&).

Une troisième solution consisterait à prévoir des surcharges supplémentaires, par exemple:

  • operator <(int, const Foo&)
  • operator <(const Foo&, int)

Ce dernier, dans cet exemple, sera alors préférable à des surcharges mentionnées ci-dessus comme une correspondance exacte, même si vous n'avez pas introduit explicit. La même chose va par exemple pour

  • operator <(const Foo&, long long)

qui serait préférable à operator <(const Foo&, const Foo&) en a < 0 parce que son utilisation ne nécessite qu'une promotion.

+0

Merci beaucoup pour cette réponse détaillée. Je marque cela comme le bon. Oui, ma quête concernait le classement du match, car il semblait bool-> int était une conversion supplémentaire. Mais il est expliqué en termes de promotion. En outre, le correctif à avoir explicitement bool opérateur, vous ne voudriez presque jamais plus de promotion. Merci aux autres aussi pour leur contribution. –

24

Le problème ici est que C++ a deux options pour traiter a < 0 expression:

  • Convertir a-bool, et de comparer les résultat à 0 avec opérateur intégré < (une conversion)
  • Convertir 0 à Foo et comparer les résultats avec < que vous avez défini (une conversion)

Les deux approches sont équivalentes au compilateur, il émet une erreur.

Vous pouvez faire cela en supprimant explicitement la conversion dans le second cas:

if (a < Foo(0)) { 
    ... 
} 
+0

convertir 'a' en' bool', oui mais 'bool' n'est pas la même chose que' int', –

+3

@jkjyuio Zero n'est pas seulement un 'int', C++ le traite comme tout type vers lequel il pourrait être converti, y compris 'int',' bool', pointeurs, flotteurs, doubles, etc. Quand zéro est traité comme 'bool', c'est la même chose que' false'. Cela ne compte pas comme une conversion. – dasblinkenlight

+7

Plus précisément, la conversion de 'bool' en' int' est une * promotion intégrale * plutôt qu'une * conversion *. Voir la section 13.3.3.2 [over.ics.rank] dans (par exemple) [n4296] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf). Oui, c'est horriblement compliqué. Évitez les conversions implicites! –

2

C'est exactement ce que le compilateur vous dit.

Une approche pour résoudre le if (a < 0) pour le compilateur est d'utiliser le constructeur Foo(int x) que vous avez fourni pour créer un objet de 0.

La seconde est d'utiliser la conversion operator bool et la comparer à la int (promotion). Vous pouvez en lire plus à ce sujet dans la section Numeric promotions. Par conséquent, il est ambigu pour le compilateur et il ne peut pas décider de quelle manière vous voulez qu'il se passe.

+0

Pourquoi comparer 'bool' vs' int' n'est pas considéré comme une conversion _additional_? –

+4

@jkj - 'bool' à' int' est une "promotion" et non une conversion. :-) Notez que le chapitre "Conversions standard" prend 7 pages à expliquer dans le document standard. Une explication complète ne tient pas sur StackOverflow. Votre problème de base est que votre classe offre des conversions implicites à la fois vers et depuis la classe. La plupart des gens évitent de le faire pour la raison précise que vous avez remarqué - cela devient confus à la fois pour les lecteurs et pour le compilateur. –

+0

@BoPersson Merci pour le catch - poste édité – Dusteh

4

Parce que le compilateur ne peut pas choisir entre bool operator <(const Foo &,const Foo &) et operator<(bool, int) qui s'adapte dans cette situation.

Afin de résoudre le problème faire second constructeur explicit:

struct Foo 
{ 
    Foo() {} 
    explicit Foo(int x) {} 

    operator bool() const { return false; } 

    friend bool operator<(const Foo& a, const Foo& b) 
    { 
     return true; 
    } 
}; 

Edit: Ok , enfin je suis un vrai point de la question :) OP demande pourquoi son compilateur offre operator<(int, int) comme candidat, bien que "les conversions en plusieurs étapes ne sont pas autorisées".

Réponse: Oui , afin d'appeler operator<(int, int) objet a doit être converti Foo -> bool -> int. Mais, C++ Standard ne dit pas que "les conversions en plusieurs étapes sont illégales".

§ 12.3.4 [classe.conv]

au plus une conversion définie par l'utilisateur (constructeur ou conversion fonction ) est implicitement appliqué à une seule valeur.

bool à intn'est pas la conversion définie par l'utilisateur, par conséquent, il est légal et compilateur a le plein droit de choisir operator<(int, int) comme candidat.

+0

non parce que le compilateur n'a pas listé l'opérateur <(bool, int). seulement (Foo, Foo) et (int, int). –

+0

Dans votre cas en effet, 'operator <(int, int)'. Cependant, MSVC 2017 répertorie 'operator <(bool, int)'. – WindyFields

+0

Cela signifie-t-il que la conversion de 'bool' en' int' ne compte pas comme une étape de conversion.Alors, ils sont le type _same_? –