9
votes

Chaîne ambiguë :: opérateur = appeler pour type avec conversion implicite sur int et chaîne

Compte tenu du programme suivant:

#include <iostream>
#include <string>
using namespace std;
struct GenericType{
   operator string(){
      return "Hello World";
   }
   operator int(){
      return 111;
   }
   operator double(){
      return 123.4;
   }
};
int main(){
   int i = GenericType();
   string s = GenericType();
   double d = GenericType();
   cout << i << s << d << endl;
   i = GenericType();
   s = GenericType(); //This is the troublesome line
   d = GenericType();
   cout << i << s << d << endl;
}


13 commentaires

Les conversions implicites sont une bonne source de problèmes. Reconsidérer si vous voulez vraiment cela ...


Je fais. Je suis surtout intéressé par cela pour les curiosités à ce stade.


Vous pouvez également utiliser opérateur explicite int () etc. en C ++ 11. Cela empêche les erreurs aussi bien que l'utilisation d'une fonction gettype () , car l'utilisateur doit faire explicitement.


Dans une version antérieure de votre commentaire, vous prétendez que vous ne voulez que cela pour la tâche et l'initialisation et que vous avez demandé si des conversions pourraient être limitées à ces deux opérations. Ils ne peuvent pas. Cela fait partie du problème des conversions, ils lancent dans des circonstances que vous pourriez ne pas vouloir qu'ils. Vous avez également inclus le modèle alternatif t get (); , eh bien, si vous voulez affectation considérer modèle VOID assigno (T &) comme cela rendra la syntaxe utilisateur plus sympathique (le compilateur déduira le type)


David, j'apprécie votre préoccupation, j'ai édité mon commentaire après avoir examiné exactement ce que vous venez d'essayer de clarifier. J'ai envie d'investir beaucoup d'efforts pour tenter de rejeter une question valable. Qu'est-ce qui compte ce que l'application est pour vous quand même? Je demande une question concise et je cherche une réponse concise.


@Mooing Duck: C'est une esquive concise. Découvrir l'incohérence du compilateur avec quelque chose qui ne devrait pas être un comportement indéfini (la résolution de la surcharge) est suffisante d'une raison à laquelle cela doit être répondu correctement. Déterminer s'il est possible de faire à travers les fournisseurs et de la compréhension de la langue. Dans tous les droits, il s'agit d'une question valable et des commentaires offerts comme les vôtres ne sont tout simplement pas appréciés ni recherchés.


@ M2TM: Ce n'est pas un comportement non défini, cela ne parvient pas à compiler. La façon d'obtenir ce que vous voulez est (comme David a dit) d'utiliser des opérateurs de conversion nommés ou explicites. Je ne sais pas pourquoi vous jeûnez simplement ce qu'il a dit, puisqu'il est la bonne réponse. (J'admets mon commentaire est né d'une irritation)


Je ne comprends pas pourquoi vous êtes irrité. Je n'ai ni essayé d'offenser, ni essayé d'ignorer les conseils. Je suis légitimement curieux et j'ai posé une belle question. S'il n'est tout simplement pas possible d'atteindre cette syntaxe, c'est une chose (je crois qu'il peut y avoir un moyen avec une combinaison de modèles et de type d'emballage si je ne le sais pas), mais ce que je suis dit est "Don ' t dérange de demander "qui en est un autre. La distinction de bullhed est pire que d'essayer de faire la mauvaise chose en premier lieu et franchement plus insultant parce que cela implique de parler à l'astucieux.


@Mooing Duck: Je vois que vous avez supprimé le commentaire original qui a commencé cette chaîne. Je suis prêt à supprimer mes commentaires si vous le souhaitez, tout cela a moins de sens avec les commentaires manquants.


Je pense que la chaîne tient assez bien sans ma remarque désinvolte. La question est bonne, et Chris et David vous ont dit la réponse dans les 20 minutes. Je suis (à tort) devenu irrité lorsque vous avez accusé David de «rejeter une question valide», mais j'ai maintenant refroidi. Résumé de base (confirmé par des réponses ci-dessous) est que cela ne peut pas être fait. Les solutions de contournement sont dans les commentaires de David et Chris.


Le problème de votre question est que vous soumettez une solution C ++ particulière (une classe avec de nombreux opérateurs de conversion), puis demandez comment le réparer. La réponse technique a été fournie et la question "Comment résoudre cette question" ne peut pas être répondue à une question avec une classe nommée génératrice : Si vous expliquez votre conception, pourquoi vous pensez avoir besoin de son, peut-être Nous pouvons fournir une "solution".


Il y aurait de la valeur pour pouvoir emballer et décompresser facilement les valeurs via l'opérateur d'accès à un type de conteneur générique pouvant sérialiser et lire dans les valeurs JSON (y compris les chaînes). Cela fonctionne pour tous les types autres que STD :: String en raison de l'opérateur de trutation de caractère. Il n'y a pas de solution pour cela, sauf une extension non officielle que Microsoft a mis en œuvre dans Visual Studio en raison du libellé de résolution de l'ambiguïté dans la norme. Connect.Microsoft.com/visualstudio/feedback/Détails/743685/...


Exemple: Jsondict ["Âge"] = 5; jsondict ["nom"] = "Howdy Doody"; std :: nom de string = jsondict ["nom"]; int Âge = JSondict ["Âge"]; Cela fonctionnera réellement, mais si vous utilisiez l'opérateur d'affectation et que STD :: Nom de la chaîne; Nom = jsondict ["nom"]; Il se casse. En renvoyant un "générateur" qui peut alors lancer correctement (ou jeter s'il s'agit du mauvais type), vous pouvez écrire un code de charge JSON plus gracieux et plus gracieux. Sinon, vous devez nommer les types deux fois: STD :: String Name = JSONCT.GET ("Nom"); J'espère que vous pourrez voir le désir ici.


4 Réponses :


10
votes

Je crois que GCC et SLIG sont corrects.

Il y a deux opérateur = surcharges en lecture: xxx

Tous deux de ces < Code> Opérateur = Les surcharges nécessitent une conversion définie par l'utilisateur de votre argument de type généricype . (1) nécessite l'utilisation de la conversion sur chaîne . (2) nécessite l'utilisation de la conversion sur int , suivie d'une conversion standard sur char .

L'important chose est que les deux surcharges nécessitent une conversion définie par l'utilisateur. Déterminer si l'une de ces conversions est meilleure que l'autre, nous pouvons rechercher les règles de résolution de la surcharge, en particulier la règle suivante de C ++ 11 §13.3.3.2 / 3 (reformaté pour la clarté):

séquence de conversion définie par l'utilisateur u1 est une meilleure séquence de conversion qu'une autre séquence de conversion définie par l'utilisateur u2 si

  1. Ils contiennent la même fonction de conversion définie par l'utilisateur ou la même initialisation du constructeur ou de l'agrégat et

  2. La deuxième séquence de conversion standard de u1 est meilleure que la seconde séquence de conversion standard de U2 .

    Notez qu'un et joint les deux parties de la règle, de sorte que les deux parties doivent être satisfaites. La première partie de la règle n'est pas satisfaite: les deux séquences de conversion définies par l'utilisateur utilisent différentes fonctions de conversion définies par l'utilisateur.

    Par conséquent, aucune conversion n'est meilleure, et l'appel est ambigu. < P> [Je n'ai pas de bonne suggestion sur la manière de résoudre le problème sans modifier la définition de principal () . Les conversions implicites ne sont généralement pas une bonne idée; Ils sont parfois très utiles, mais plus souvent, ils risquent de causer des ambiguïtés de surcharge ou un autre comportement étrange de surcharge étrange.]

    Il y avait un rapport de bogue GCC dans lequel ce problème a été décrit et résolu comme par conception: < Un href = "http://gcc.gnu.org/bugzilla/show_bug.cgi?id=14830" rel = "noreferrer"> compilateur diagnostite de manière incorrecte la surcharge de l'opérateur ambiguë.


3 commentaires

J'aimerais indiquer que l'en quelque sorte Visual Studio parvient à le traiter correctement: String Val; val = 65; COUT << (VAL == 'A'); Je me demande si c'est une différence de résolution du compilateur. Je suppose que vous avez raison que GCC et Scrang sont corrects, je suppose que je vais suivre un bogue avec Microsoft et voir si c'est le cas. J'espérais vraiment qu'une sorte d'astuce indirecte pour gérer correctement la surcharge dans l'attribution directe à un int sans avoir besoin d'une distribution, mais pour déshonver la conversion implicite si les types ne correspondent pas précisément.


Connect.Microsoft.com/visualstudio/feedback/Détails/743685/...


Microsoft a eu l'un de leurs développeurs confirmer ce défaut. Je pense que c'est une honte personnellement, je pense que la définition de la résolution de la surcharge de la norme pourrait probablement faire un meilleur travail de résolution de ce type d'ambiguïté personnellement. Dans le cas de deux chaînes d'appels impliquant un utilisateur spécifié Cast, celui qui entraîne le résultat précis pourrait facilement être considéré comme le meilleur (sur un qui nécessite une nouvelle distribution entre types de pod).



2
votes

Je crois que GCC est faux. Dans le livre "Le langage de programmation C ++" de Bjarne Stroustrup, il y a tout un chapitre consacré à la surcharge de l'opérateur. Dans la section 11.4.1, le premier paragraphe dit que:

"Une affectation d'une valeur de type V à un objet de classe X est légale s'il y a un opérateur d'attribution x :: opérateur = (z) de sorte que v est z ou une conversion unique de V sur Z. L'initialisation est traitée de manière équivalente. "

dans votre exemple, GCC accepte" String S = généricype (); " Pourtant, rejette "s = généraliste ();", donc il ne traite évidemment pas l'affectation de la même manière que l'initialisation. C'était mon premier indice que quelque chose est absent dans GCC.

GCC rapporte 3 candidats à la conversion de généraliste de la cordes dans la mission, tous dans basic_string.h. L'une est la conversion correcte, l'un des rapports informatiques n'est pas valide et la troisième provoque l'ambiguïté. Il s'agit de la surcharge de l'opérateur dans basic_string.h qui provoque l'ambiguïté: xxx

Ce n'est pas une conversion valide car elle accepte un opérande qui ne correspond pas à un opérande qui ne correspond pas à la type d'objet qui lui est transmis. Dans aucun endroit, il n'ya pas d'affectation à une tentative de charcuterie, cette conversion ne devrait donc pas être un candidat beaucoup moins cher qui provoque une ambiguïté. GCC semble mélanger le type de modèle avec le type d'opérande dans son membre.

EDIT: Je ne savais pas que l'attribution d'un entier à une chaîne était en fait légal, comme un entier Peut être converti en un char, lequel peut être affecté à une chaîne (bien qu'une chaîne ne puisse pas être initialisée à un caractère!). Généricype définit une conversion vers un INT, ce qui rend ce membre un candidat valide. Cependant, je maintiens toujours que ce n'est pas une conversion valable, la raison étant que l'utilisation de cette conversion entraînerait deux conversions implicites définies par l'utilisateur pour la mission, d'abord du générateur à int, puis de Int à la chaîne. Comme indiqué dans le même livre 11.4.1, "Un seul niveau de conversion implicite définie par l'utilisateur est légal."


8 commentaires

Je crois que la raison pour laquelle il n'échoue pas la construction de GCC n'est pas à cause de l'incohérence interne avec le compilateur, mais que la classe de base_string n'a pas de constructeur qui prend _chart (seul un opérateur d'affectation qui). Je suppose que si cela faisait alors cela échouerait également sur la construction.


Ce serait parce que l'initialisation d'un caractère à une chaîne est une exception à cette règle. Initialiser une chaîne avec un caractère n'est pas valide, mais l'attribution d'un caractère à une chaîne est valide, comme indiqué dans le même livre de la section 20.3.7.


L'étant comparé est de type chaîne de type, mais l'opérande de cette surcharge prend un char. Pour que cela soit un candidat acceptable, un caractère devrait être initialisé avec la valeur d'une chaîne. Comme: Char c = string (v). Cela n'a aucun sens! Bien que vous puissiez attribuer un caractère à une chaîne, vous ne pouvez pas attribuer une chaîne à un caractère, qui est la conversion nécessaire pour ce candidat. Comme cette conversion n'est pas valide, cela devrait donc être ce candidat et il ne devrait donc pas y avoir d'ambiguïté.


J'ai regardé les chapitres. Je vois ce que vous voulez dire, mais le générateur peut être implicitement converti en int, et INT peut être converti en un caractère (comme un caractère peut être converti en int). Pour clarifier, je crois que l'ambiguïté est entre les deux conversions: 1) String :: Opérateur = (static_cast (statique_cast (type générique ())) (Les couts sont écrits explicitement ici, mais ils arriver implicitement dans GCC et correspond à la chaîne :: opérateur = (char)). 2) chaîne :: opérateur = (static_cast (généricype ())). Où String :: opérateur = (char) et chaîne :: opérateur = (Const String &) sont les deux prétendeurs.


Si mon explication ci-dessus est correcte, cela signifie qu'il existe une divergence de compilateur où Visual Studio reconnaît qu'il faut une inconvénalité de l'option 1 que l'option 2 et résout ainsi l'ambiguïté de cette manière, mais GCC et Clang n'utilisent pas cette information à résoudre l'ambiguïté. Si tel est le cas (et je crois que c'est), l'un ou l'autre est incorrect (ou cela peut être indéfini, mais je doute que c'est).


Si c'est GCC / CLANG qui gère de manière incorrecte la substitution, la réponse est que j'ai écrit ce qui précède correctement et qu'ils ont besoin de réparer leur compilateur, si Visual Studio a le bogue puis je suis intéressé par un travail intelligent / académique qui existe si l'on existe. , ou une explication de la raison pour laquelle il est réellement impossible (plutôt que pour pourquoi ce n'est pas une bonne idée.)


"Le généricype peut être implicitement converti en int, et int peut être converti en un char" qui peut ensuite être converti en une chaîne, vous êtes juste là. Cependant, à ce stade, nous sommes à 2 conversions implicites définies par l'utilisateur (génératrice générale-> Int-> String). En se référant à 11.4.1 "Un seul niveau de conversion implicite défini par l'utilisateur est légal", toujours invalidant ce candidat.


Non, il n'y a toujours qu'une conversion définie par l'utilisateur dans ce cas. N'oubliez pas que le type cible n'est pas chaîne , il est char , car c'est le type du paramètre de fonction. Par conséquent, il y a une conversion définie par l'utilisateur ( généricype sur int ) suivie d'une conversion standard ( int sur char ). J'ai ajouté à ma réponse à la langue exacte de C ++ 11 qui exige que l'appel est ambigu.



2
votes

Ma question est la suivante: Comment puis-je résoudre cette ambiguïté sans modifier le contenu de la principale?

Créez votre propre classe nommée String qui n'a pas d'opérateur ambigu = , puis ne en utilisant le std un.

Évidemment, ce n'est pas une très bonne solution, mais cela fonctionne et principal ne doit pas changer.

Je ne pense pas que vous puissiez obtenir le comportement que vous voulez d'autre manière.


1 commentaires

Ceci est précis, mais pas aussi utile que la question que j'ai finalement attribuée à une prime. Merci, cependant, vous avez fourni une réponse correcte et raisonnable.



1
votes

Cette solution fonctionne xxx pré>

et une solution plus générale. Je pense que vous n'avez pas besoin de créer des opérateurs pour chaque type arithmétique, car les conversions implicites feront le tour. P>

// ...

template <typename T, typename = std::enable_if_t 
    <std::is_arithmetic<T>::value && !std::is_same<T, char>::value>>
operator T() const
{
    return std::stod("123.4");
}

//...


1 commentaires

Merci beaucoup pour cette réponse! En fait, c'est la seule solution qui a fonctionné pour moi. Je crois que cela est dû au modèle introduisant un autre niveau d'indirection, non?