Pourquoi cette surcharge d'un opérateur de conversion est-elle choisie?

27

Considérez le code suivant .

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

Ici, le deuxième opérateur de conversion est choisi par la résolution de surcharge. Pourquoi?

D'après ce que je comprends, les deux opérateurs sont déduits de operator int &&() constet operator int &() constrespectivement. Les deux font partie de l'ensemble des fonctions viables. La lecture de [over.match.best] ne m'a pas aidé à comprendre pourquoi ce dernier est meilleur.

Pourquoi cette dernière fonction est-elle meilleure que la première?

avakar
la source
Je suis un simple chat et je dirais que ce doit être la seconde fois que l'introduction de l'autre aurait été un changement de rupture en C ++ 11. Ce sera quelque part dans la norme. Bonne question cependant, ayez un vote positif. (Attention, les experts: si ce commentaire est de la merde, dites-le moi!)
Bathsheba
3
FWIW, le template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;fait appeler le premier.
NathanOliver
5
@LightnessRaceswithMonica Les opérateurs de conversion le permettent sinon il n'y aurait aucun moyen d'avoir plusieurs opérateurs de conversion.
NathanOliver
1
@Bathsheba Je ne pense pas que ce soit la raison. Cela revient à dire que les constructeurs de déplacement ne peuvent jamais être sélectionnés par la résolution de surcharge, car ce serait un changement de rupture. Si vous écrivez un constructeur de déplacement, vous acceptez que votre code soit "cassé" par cette définition.
Brian
1
@LightnessRaceswithMonica La façon dont je le garde bien dans ma tête est que je traite le type de retour comme le nom de la fonction. Différents types sont différents noms sont
synonymes de

Réponses:

13

L'opérateur de conversion qui retourne T&est préféré car il est plus spécialisé que l'opérateur de conversion qui retourne T&&.

Voir C ++ 17 [temp.deduct.partial] / (3.2):

Dans le contexte d'un appel à une fonction de conversion, les types de retour des modèles de fonction de conversion sont utilisés.

et / 9:

Si, pour un type donné, la déduction réussit dans les deux sens (ie, les types sont identiques après les transformations ci-dessus) et les deux Pet Aétaient des types de référence (avant d'être remplacés par le type mentionné ci-dessus): - si le type du modèle d'argument était une référence lvalue et le type du modèle de paramètre ne l'était pas, le type de paramètre n'est pas considéré comme au moins aussi spécialisé que le type d'argument; ...

Brian
la source
12

Les opérateurs de conversion de valeur de retour déduits sont un peu étranges. Mais l'idée centrale est qu'il agit comme un paramètre de fonction pour choisir lequel est utilisé.

Et pour décider entre T&&et T&les T&victoires dans les règles de résolution de surcharge. Ceci permet:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

travailler. T&&peut correspondre à une valeur l, mais lorsque la surcharge lvalue et la référence universelle sont disponibles, la valeur lvalue est préférée.

Le bon ensemble d'opérateurs de conversion est probablement:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

ou même

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

pour éviter que l'extension de vie échouée ne vous mord.

3 Les types utilisés pour déterminer l'ordre dépendent du contexte dans lequel l'ordre partiel est effectué:

[COUPER]

(3.2) Dans le contexte d'un appel à une fonction de conversion, les types de retour des modèles de fonction de conversion sont utilisés.

Ce qui finit alors par dépendre de règles "plus spécialisées" lors du choix des surcharges:

(9.1) si le type du modèle d'argument était une référence lvalue et que le type du modèle de paramètre ne l'était pas, le type de paramètre n'est pas considéré comme au moins aussi spécialisé que le type d'argument; autrement,

Ainsi, il operator T&&n'est pas au moins aussi spécialisé que operator T&, pendant ce temps, aucun état de règle operator T&n'est pas au moins aussi spécialisé que operator T&&, donc il operator T&est plus spécialisé que operator T&&.

Des modèles plus spécialisés gagnent moins de résolution de surcharge, toutes choses étant égales par ailleurs.

Yakk - Adam Nevraumont
la source
Quelqu'un a ajouté la balise langue-avocat pendant que je répondais; Je pourrais simplement supprimer. J'ai un vague souvenir de lire le texte qui déclare qu'il est traité comme un paramètre pour choisir lequel est appelé, et le texte qui parle de la façon dont &&vs &est traité lors du choix des surcharges de modèles, mais taquiner le texte exact prendrait un certain temps .
Yakk - Adam Nevraumont
4

Nous essayons d'initialiser un à intpartir d'un any. Le processus pour cela:

  1. Découvrez tout ce que nous pouvons faire. Autrement dit, déterminez tous nos candidats. Celles-ci proviennent des fonctions de conversion non explicites qui peuvent être converties en intvia une séquence de conversion standard ( [over.match.conv] ). La section contient cette phrase:

    Un appel à une fonction de conversion renvoyant "référence à X" est une valeur de type gl X, et une telle fonction de conversion est donc considérée comme donnant lieu Xà ce processus de sélection de fonctions candidates.

  2. Choisissez le meilleur candidat.

Après l'étape 1, nous avons deux candidats. operator int&() constet operator int&&() const, les deux sont considérés comme cédant intaux fins de la sélection des fonctions candidates. Quel est le meilleur candidat qui donne int?

Nous avons un bris d'égalité qui préfère les références lvalue aux références rvalue ( [over.ics.rank] /3.2.3 ). Nous ne lions pas vraiment une référence ici cependant, et l'exemple il est quelque peu inversé - c'est pour le cas où le paramètre est une référence lvalue vs rvalue.

Si cela ne s'applique pas, nous passons au bris d'égalité [over.match.best] /2.5 de préférer le modèle de fonction plus spécialisé.

De manière générale, la règle générale est que la conversion la plus spécifique est la meilleure correspondance. La fonction de conversion de référence lvalue est plus spécifique que la fonction de conversion de référence de transfert, elle est donc préférable. Il n'y a rien dans l' intinitialisation qui nécessite une valeur r (si nous avions plutôt initialisé une int&&, alors l' operator T&() constaurait pas été candidat).

Barry
la source
3.2.3 dit que les T&&victoires ne le sont pas? Ah, mais nous n'avons pas de valeur à laquelle nous sommes liés. Ou le faisons-nous? Lors de la sélection de nos candidats, certains mots doivent avoir défini comment nous avons obtenu int&et int&&, était-ce une obligation?
Yakk - Adam Nevraumont
@ Yakk-AdamNevraumont Non, on préfère les références lvalue aux références rvalue. Je pense que c'est probablement la mauvaise section de toute façon, et le bris d'égalité "plus spécialisé" est le bon.
Barry