Question Préséance de la fonction membre const sur la correspondance de type valeur de retour


Dans Y::test1() un non-const X::operator void*() prend le pas sur un match apparemment meilleur, X::operator bool() const - Pourquoi donc? Et où est ce phénomène décrit dans la norme?

#include <iostream>

struct X {
  operator void*()      { std::cout << "  operator void*()\n"; return nullptr; }
  operator bool() const { std::cout << "  operator bool()\n";  return true; }
};

struct Y {
  X x;
  bool test1()       { std::cout << "test1()\n"; return x; }
  bool test2() const { std::cout << "test2()\n"; return x; }
};

int main() {
  Y y;
  y.test1();
  y.test2();
}

Sortie:

test1()
  operator void*()
test2()
  operator bool()

16
2017-09-13 08:25


origine


Réponses:


Tout d'abord: lors de la conversion de l'expression dans un return instruction au type de retour de la fonction, les règles sont les mêmes que pour l'initialisation (voir [conv] /2.4 et [conv] / 3).

Donc, nous pourrions examiner le comportement du code en utilisant cet exemple à la place (avec le même X comme vous avez, mais sans Y):

X test1;
bool b1 = test1;

X const test2;
bool b2 = test2;

(dans l'appel y.test2(), le type de this->x est X const, c'est ce que signifie avoir une fonction membre const). Ce serait aussi la même chose si on joignait à bool au lieu d'écrire une instruction d'initialisation.


La partie de la norme traitant de la résolution de surcharge dans cette situation est [over.match.conv], voici le texte C ++ 14 (avec une certaine concision):

13.3.1.5 Fonction d'initialisation par conversion [over.match.conv]

1 Dans les conditions spécifiées en 8.5, dans le cadre d'une initialisation d'un objet de type non-classe, une fonction de conversion peut être appelée pour convertir une expression d'initialisation de type de classe en type d'objet en cours d'initialisation. [...]

2 La liste d'arguments a un argument, qui est l'expression d'initialisation. [Remarque: Cet argument sera comparé au paramètre objet implicite des fonctions de conversion. -End note ]

le test2 cas est simple - une fonction membre non-const ne peut pas être appelée sur un objet const, donc la operator void* n'est jamais considéré, il n'y a qu'un seul candidat et pas besoin de résolution de surcharge. operator bool() est appelé.

Donc, pour le reste de cet article, je vais juste parler de la test1 Cas. La partie que j'ai éludée dans la citation ci-dessus couvre à la fois operator bool et operator void*() sont des fonctions candidates pour le test1 Cas.


Il est important de noter que la résolution de surcharge sélectionne parmi ces deux fonctions candidates, et c'est ne pas un cas de prise en compte de deux séquences de conversion implicites, chacune contenant une conversion définie par l'utilisateur. Pour cela, regardez la première phrase de [over.best.ics]:

Un séquence de conversion implicite est une séquence de conversions utilisée pour convertir un argument dans un appel de fonction au type du paramètre correspondant de la fonction appelée.

Nous ne convertissons pas un argument dans un appel de fonction ici. Les règles relatives aux séquences de conversion implicites entrent en jeu lorsque nous classons les fonctions candidates: les règles sont appliquées à chaque argument de chaque fonction candidate, comme nous le verrons dans un instant.


Alors maintenant, nous regardons les règles pour meilleure fonction viable pour déterminer laquelle de ces deux fonctions candidates est sélectionnée. Je vais sauter [over.match.viable], ce qui clarifie que ces deux candidats sont viables, et sur [over.match.best].

La partie clé de cette section est [over.match.best] /1.2:

laisser ICSi (F) dénote la séquence de conversion implicite qui convertit le i-ème argument de la liste en type du i-ème paramètre de la fonction viable F. 13.3.3.1 définit les séquences de conversion implicites et 13.3.3.2 définit ce que cela signifie pour une séquence de conversion implicite être une meilleure séquence de conversion   ou pire séquence de conversion qu'un autre.

Ici, i == 1, il n'y a qu'un seul argument test1 comme expliqué par [over.match.conv] / 2 ci-dessus - l'argument est l'expression de l'initialiseur test1; le "paramètre de la fonction viable" est le paramètre objet implicite des fonctions membres de X.

À présent les règles de séquence de conversion implicite s'appliquent:

  • operator void*() ne nécessite aucune conversion - l'argument est X et le paramètre est X&
  • operator bool() const nécessite une conversion de qualification - l'argument est X et le paramètre est X const&

Pas de conversion vaut mieux que la conversion de qualification ([over.ics.rank] / 3.1.1). Donc ICS1 (operator void*()) est une meilleure séquence de conversion que ICS1 (operator bool() const); alors à ce stade operator void*() gagne ([over.match.best] /1.3).

Le paragraphe suivant [over.match.best] /1.4 explique ce qui se serait passé si aucune de ces deux séquences n’était meilleure: nous ne ferions alors que comparer les séquences de conversion standard du type de retour de la fonction candidate au type étant initialisé.

Vous pouvez explorer ce cas en changeant le X membre à operator void*() const. Maintenant, les deux séquences ICS1 sont indiscernables à partir de /1.3, nous allons donc sur /1.4 à quel point operator bool() const gagne parce que sa conversion à bool est l'identité, alors que operator void*() const nécessite toujours une conversion booléenne.


10
2017-09-13 10:12



L'implicite this le pointeur est nonconst. Alors résolution de surcharge se déroule sur le plateau de tout non-const méthodes (y compris les modèles - qui est l'un pour le quiz pub), avant tout const les méthodes sont considérées.

Le fait qu'une fonction soit marquée const ne rend jamais un meilleur ajustement pour un nonconst  this pointeur, si vous avez ma signification.

Voir http://fr.cppreference.com/w/cpp/language/overload_resolution ce qui correspond en grande partie aux normes.


7
2017-09-13 08:29