Recherche…


Remarques

La résolution de surcharge se produit dans plusieurs situations différentes

  • Appels aux fonctions surchargées nommées. Les candidats sont toutes les fonctions trouvées par la recherche de nom.
  • Appels à un objet de classe. Les candidats sont généralement tous les opérateurs d'appel de fonctions surchargés de la classe.
  • Utilisation d'un opérateur Les candidats sont les fonctions d'opérateur surchargées dans la portée de l'espace de noms, les fonctions d'opérateur surchargées dans l'objet de classe de gauche (le cas échéant) et les opérateurs intégrés.
  • Résolution de surcharge pour trouver la fonction ou le constructeur d'opérateur de conversion correct à appeler pour une initialisation
    • Pour l'initialisation directe sans liste ( Class c(value) ), les candidats sont des constructeurs de Class .
    • Pour une initialisation de copie sans liste ( Class c = value ) et pour rechercher la fonction de conversion définie par l'utilisateur à invoquer dans une séquence de conversion définie par l'utilisateur. Les candidats sont les constructeurs de Class et si la source est un objet de classe, ses fonctions d'opérateur de conversion.
    • Pour l'initialisation d'une non-classe à partir d'un objet de classe ( Nonclass c = classObject ). Les candidats sont les fonctions d'opérateur de conversion de l'objet d'initialisation.
    • Pour initialiser une référence avec un objet de classe ( R &r = classObject ), lorsque la classe a des fonctions d'opérateur de conversion qui fournissent des valeurs pouvant être directement liées à r . Les candidats sont des fonctions d'opérateur de conversion.
    • Pour l'initialisation de liste d'un objet de classe non agrégé ( Class c{1, 2, 3} ), les candidats sont les constructeurs de listes d'initialisation pour une première résolution de surcharge. Si cela ne permet pas de trouver un candidat viable, une résolution de surcharge est effectuée avec les constructeurs de Class comme candidats.

Correspondance exacte

Une surcharge sans conversions nécessaire pour les types de paramètres ou uniquement les conversions nécessaires entre les types qui sont toujours considérés comme des correspondances exactes est préférable à une surcharge qui nécessite d'autres conversions pour appeler.

void f(int x);
void f(double x);
f(42); // calls f(int)

Lorsqu'un argument est lié à une référence au même type, la correspondance est considérée comme ne nécessitant pas de conversion même si la référence est plus qualifiée cv.

void f(int& x);
void f(double x);
int x = 42;
f(x); // argument type is int; exact match with int&

void g(const int& x);
void g(int x);
g(x); // ambiguous; both overloads give exact match

Aux fins de la résolution de surcharge, le type "tableau de T " est considéré comme correspondant exactement au type "pointeur sur T ", et le type de fonction T est considéré comme correspondant exactement au pointeur de fonction de type T* , même si les deux nécessitent conversions

void f(int* p);
void f(void* p);

void g(int* p);
void g(int (&p)[100]);

int a[100];
f(a); // calls f(int*); exact match with array-to-pointer conversion
g(a); // ambiguous; both overloads give exact match

Catégorisation de l'argument au coût du paramètre

La résolution de surcharge partitionne le coût de la transmission d'un argument à un paramètre en quatre catégories différentes, appelées "séquences". Chaque séquence peut inclure zéro, une ou plusieurs conversions

  • Séquence de conversion standard

    void f(int a); f(42);
    
  • Séquence de conversion définie par l'utilisateur

    void f(std::string s); f("hello");
    
  • Séquence de conversion des ellipses

    void f(...); f(42);
    
  • Séquence d'initialisation de la liste

    void f(std::vector<int> v); f({1, 2, 3});
    

Le principe général est que les séquences de conversion standard sont les moins chères, suivies des séquences de conversion définies par l'utilisateur, suivies des séquences de conversion des points de suspension.

Un cas particulier est la séquence d'initialisation de la liste, qui ne constitue pas une conversion (une liste d'initialisation n'est pas une expression avec un type). Son coût est déterminé en le définissant comme étant équivalent à l'une des trois autres séquences de conversion, en fonction du type de paramètre et de la forme de la liste d'initialisation.

Recherche de nom et contrôle d'accès

La résolution de la surcharge se produit après la recherche du nom. Cela signifie qu'une fonction mieux adaptée ne sera pas sélectionnée par une résolution de surcharge si elle perd la recherche de nom:

void f(int x);
struct S {
    void f(double x);
    void g() { f(42); } // calls S::f because global f is not visible here,
                        // even though it would be a better match
};

La résolution de la surcharge se produit avant la vérification de l'accès. Une fonction inaccessible peut être sélectionnée par résolution de surcharge si elle correspond mieux qu’une fonction accessible.

class C {
  public:
    static void f(double x);
  private:
    static void f(int x);
};
C::f(42); // Error! Calls private C::f(int) even though public C::f(double) is viable.

De même, la résolution de la surcharge se produit sans vérifier si l'appel résultant est bien formé en ce qui concerne l' explicit :

struct X {
    explicit X(int );
    X(char );
};

void foo(X );
foo({4}); // X(int) is better much, but expression is 
          // ill-formed because selected constructor is explicit

Surcharge sur la référence de transfert

Vous devez être très prudent lorsque vous fournissez une surcharge de référence de transfert car elle peut trop bien correspondre:

struct A {
    A() = default;           // #1
    A(A const& ) = default;  // #2

    template <class T>
    A(T&& );                 // #3
};

L'intention ici était que A soit copiable, et que nous ayons cet autre constructeur qui pourrait initialiser un autre membre. Toutefois:

A a;     // calls #1
A b(a);  // calls #3!

Il y a deux correspondances viables pour l'appel de construction:

A(A const& ); // #2
A(A& );       // #3, with T = A&

Les deux sont des correspondances exactes, mais le #3 fait référence à un objet moins qualifié cv que le #2 , il a donc la meilleure séquence de conversion standard et est la meilleure fonction viable.

La solution ici est de toujours contraindre ces constructeurs (par exemple en utilisant SFINAE):

template <class T,
    class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
    >
A(T&& );

Le trait de type ici est d'exclure toute classe A ou dérivée publiquement et sans ambiguïté de A , ce qui rendrait ce constructeur mal formé dans l'exemple décrit précédemment (et donc supprimé de l'ensemble de surcharge). En conséquence, le constructeur de copie est appelé - ce que nous voulions.

Étapes de la résolution de surcharge

Les étapes de la résolution de la surcharge sont les suivantes:

  1. Rechercher des fonctions candidates via la recherche de nom. Les appels non qualifiés effectueront à la fois une recherche régulière non qualifiée et une recherche dépendante des arguments (le cas échéant).

  2. Filtrer l'ensemble des fonctions candidates sur un ensemble de fonctions viables . Une fonction viable pour laquelle il existe une séquence de conversion implicite entre les arguments avec lesquels la fonction est appelée et les paramètres pris par la fonction.

    void f(char);          // (1)
    void f(int ) = delete; // (2)
    void f();              // (3)
    void f(int& );         // (4)
    
    f(4); // 1,2 are viable (even though 2 is deleted!) 
          // 3 is not viable because the argument lists don't match
          // 4 is not viable because we cannot bind a temporary to 
          //     a non-const lvalue reference
    
  3. Choisissez le meilleur candidat viable. Une fonction viable F1 est une fonction meilleure qu'une autre fonction viable F2 si la séquence de conversion implicite pour chaque argument dans F1 n'est pas pire que la séquence de conversion implicite correspondante dans F2 , et ...:

    3.1. Pour certains arguments, la séquence de conversion implicite pour cet argument dans F1 est une meilleure séquence de conversion que pour cet argument dans F2 , ou

    void f(int );  // (1)
    void f(char ); // (2)
    
    f(4);  // call (1), better conversion sequence
    

    3.2. Dans une conversion définie par l'utilisateur, la séquence de conversion standard du retour de F1 au type de destination est une meilleure séquence de conversion que celle du type de retour de F2 , ou

    struct A 
    {
        operator int();
        operator double();
    } a;
    
    int i = a; // a.operator int() is better than a.operator double() and a conversion
    float f = a; // ambiguous
    

    3.3. Dans une liaison de référence directe, F1 n'a pas le même type de référence que F2 ou

    struct A 
    {
        operator X&();  // #1
        operator X&&(); // #2
    };
    A a;
    X& lx = a;  // calls #1
    X&& rx = a; // calls #2
    

    3.4. F1 n'est pas une spécialisation de modèle de fonction, mais F2 est ou

    template <class T> void f(T ); // #1
    void f(int );                  // #2
    
    f(42); // calls #2, the non-template
    

    3.5. F1 et F2 sont tous deux des spécialisations de modèles de fonctions, mais F1 est plus spécialisé que F2 .

    template <class T> void f(T );  // #1
    template <class T> void f(T* ); // #2
    
    int* p;
    f(p); // calls #2, more specialized
    

La commande ici est significative. La meilleure vérification de la séquence de conversion se produit avant la vérification du modèle par rapport au modèle. Cela conduit à une erreur commune avec la surcharge sur la référence de transfert:

struct A {
    A(A const& ); // #1
    
    template <class T>
    A(T&& );      // #2, not constrained
};

A a;
A b(a); // calls #2!
        // #1 is not a template but #2 resolves to
        // A(A& ), which is a less cv-qualified reference than #1
        // which makes it a better implicit conversion sequence

S'il n'y a pas de meilleur candidat viable à la fin, l'appel est ambigu:

void f(double ) { }
void f(float ) { }

f(42); // error: ambiguous

Promotions arithmétiques et conversions

La conversion d'un type entier en type promu correspondant est préférable à sa conversion en un autre type entier.

void f(int x);
void f(short x);
signed char c = 42;
f(c); // calls f(int); promotion to int is better than conversion to short
short s = 42;
f(s); // calls f(short); exact match is better than promotion to int

Il est préférable de double un float que de le convertir en un autre type en virgule flottante.

void f(double x);
void f(long double x);
f(3.14f); // calls f(double); promotion to double is better than conversion to long double

Les conversions arithmétiques autres que les promotions ne sont ni meilleures ni pires les unes que les autres.

void f(float x);
void f(long double x);
f(3.14); // ambiguous

void g(long x);
void g(long double x);
g(42); // ambiguous
g(3.14); // ambiguous

Par conséquent, pour éviter toute ambiguïté lors de l'appel d'une fonction f avec des arguments intégraux ou à virgule flottante de n'importe quel type standard, un total de huit surcharges est nécessaire, de sorte que pour chaque type d'argument possible, une surcharge corresponde exactement ou la surcharge unique avec le type d’argument promu sera sélectionnée.

void f(int x);
void f(unsigned int x);
void f(long x);
void f(unsigned long x);
void f(long long x);
void f(unsigned long long x);
void f(double x);
void f(long double x);

Surcharge dans une hiérarchie de classes

Les exemples suivants utiliseront cette hiérarchie de classes:

struct A { int m; };
struct B : A {};
struct C : B {};

La conversion du type de classe dérivé en type de classe de base est préférable à celle des conversions définies par l'utilisateur. Cela s'applique lors du passage par valeur ou par référence, ainsi que lors de la conversion de pointeur en dérivé en pointeur à base.

struct Unrelated {
    Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)

Une conversion de pointeur de classe dérivée en classe de base est également préférable à la conversion en void* .

void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)

S'il y a plusieurs surcharges dans la même chaîne d'héritage, la surcharge de classe de base la plus dérivée est préférable. Ceci est basé sur un principe similaire à la répartition virtuelle: l'implémentation "la plus spécialisée" est choisie. Cependant, la résolution de la surcharge se produit toujours au moment de la compilation et ne sera jamais implicitement convertie.

void f(const A& a);
void f(const B& b);
C c;
f(c); // calls f(const B&)
B b;
A& r = b;
f(r); // calls f(const A&); the f(const B&) overload is not viable

Pour les pointeurs vers les membres, qui sont contravariants par rapport à la classe, une règle similaire s'applique dans la direction opposée: la classe dérivée la moins dérivée est préférable.

void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)

Surcharge de constance et de volatilité

Passer un argument de pointeur à un paramètre T* , si possible, est préférable à le passer à un paramètre const T* .

struct Base {};
struct Derived : Base {};
void f(Base* pb);
void f(const Base* pb);
void f(const Derived* pd);
void f(bool b);

Base b;
f(&b); // f(Base*) is better than f(const Base*)
Derived d;
f(&d); // f(const Derived*) is better than f(Base*) though;
       // constness is only a "tie-breaker" rule

De même, il est préférable de transmettre un argument à un paramètre T& , si possible, à un paramètre const T& même si les deux ont un classement exact.

void f(int& r);
void f(const int& r);
int x;
f(x); // both overloads match exactly, but f(int&) is still better
const int y = 42;
f(y); // only f(const int&) is viable

Cette règle s'applique également aux fonctions membres qualifiées const, où il est important d'autoriser l'accès mutable aux objets non const et l'accès immuable aux objets const.

class IntVector {
  public:
    // ...
    int* data() { return m_data; }
    const int* data() const { return m_data; }
  private:
    // ...
    int* m_data;
};
IntVector v1;
int* data1 = v1.data();       // Vector::data() is better than Vector::data() const;
                              // data1 can be used to modify the vector's data
const IntVector v2;
const int* data2 = v2.data(); // only Vector::data() const is viable;
                              // data2 can't be used to modify the vector's data

De la même manière, une surcharge volatile sera moins privilégiée qu'une surcharge non volatile.

class AtomicInt {
  public:
    // ...
    int load();
    int load() volatile;
  private:
    // ...
};
AtomicInt a1;
a1.load(); // non-volatile overload preferred; no side effect
volatile AtomicInt a2;
a2.load(); // only volatile overload is viable; side effect
static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow