Buscar..


Observaciones

La resolución de sobrecarga ocurre en varias situaciones diferentes

  • Llamadas a funciones sobrecargadas con nombre. Los candidatos son todas las funciones encontradas por búsqueda de nombre.
  • Llamadas a objeto de clase. Los candidatos suelen ser todos los operadores de llamada de función sobrecargados de la clase.
  • Uso de un operador. Los candidatos son las funciones de operador sobrecargadas en el ámbito del espacio de nombres, las funciones de operador sobrecargadas en el objeto de la clase izquierda (si existe) y los operadores integrados.
  • Resolución de sobrecarga para encontrar la función de operador de conversión correcta o el constructor a invocar para una inicialización
    • Para la inicialización directa no de lista ( Class c(value) ), los candidatos son constructores de Class .
    • Para la inicialización de copia no de lista ( Class c = value ) y para encontrar la función de conversión definida por el usuario para invocar en una secuencia de conversión definida por el usuario. Los candidatos son los constructores de Class y si la fuente es un objeto de clase, su operador de conversión funciona.
    • Para la inicialización de una no clase desde un objeto de clase ( Nonclass c = classObject ). Los candidatos son las funciones del operador de conversión del objeto inicializador.
    • Para inicializar una referencia con un objeto de clase ( R &r = classObject ), cuando la clase tiene funciones de operador de conversión que producen valores que pueden vincularse directamente a r . Los candidatos son tales funciones de operador de conversión.
    • Para la inicialización de lista de un objeto de clase no agregado ( Class c{1, 2, 3} ), los candidatos son los constructores de la lista de inicializadores para una primera pasada a través de la resolución de sobrecarga. Si esto no encuentra un candidato viable, se realiza un segundo paso a través de la resolución de sobrecarga, con los constructores de Class como candidatos.

Coincidencia exacta

Una sobrecarga sin conversiones necesarias para tipos de parámetros o solo conversiones necesarias entre tipos que aún se consideran coincidencias exactas es preferible a una sobrecarga que requiere otras conversiones para poder llamar.

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

Cuando un argumento se enlaza con una referencia del mismo tipo, se considera que la coincidencia no requiere una conversión, incluso si la referencia es más calificada como 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

A los efectos de la resolución de sobrecarga, se considera que el tipo "matriz de T " coincide exactamente con el tipo "puntero a T ", y se considera que la función tipo T coincide exactamente con el tipo de indicador de función T* , aunque ambos requieren conversiones

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

Categorización de argumento a costo de parámetro

La resolución de sobrecarga divide el costo de pasar un argumento a un parámetro en una de cuatro categorías diferentes, llamadas "secuencias". Cada secuencia puede incluir cero, una o varias conversiones.

  • Secuencia de conversión estándar

    void f(int a); f(42);
    
  • Secuencia de conversión definida por el usuario

    void f(std::string s); f("hello");
    
  • Secuencia de conversión elipsis

    void f(...); f(42);
    
  • Secuencia de inicialización de lista

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

El principio general es que las secuencias de conversión estándar son las más baratas, seguidas de las secuencias de conversión definidas por el usuario, seguidas de las secuencias de conversión de puntos suspensivos.

Un caso especial es la secuencia de inicialización de lista, que no constituye una conversión (una lista de inicializador no es una expresión con un tipo). Su costo se determina definiéndolo como equivalente a una de las otras tres secuencias de conversión, según el tipo de parámetro y la forma de la lista de inicializadores.

Búsqueda de nombres y verificación de acceso

La resolución de sobrecarga ocurre después de la búsqueda del nombre. Esto significa que no se seleccionará una función de mejor coincidencia por resolución de sobrecarga si pierde búsqueda de nombre:

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 resolución de sobrecarga ocurre antes de la verificación de acceso. Una resolución inaccesible puede seleccionarse por resolución de sobrecarga si es una mejor coincidencia que una función accesible.

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 manera similar, la resolución de sobrecarga ocurre sin verificar si la llamada resultante está bien formada con respecto a lo 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

Sobrecarga en la referencia de reenvío

Debe tener mucho cuidado al proporcionar una sobrecarga de referencia de reenvío, ya que puede coincidir demasiado bien:

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

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

La intención aquí era que A es copiable, y que tenemos este otro constructor que podría inicializar a otro miembro. Sin embargo:

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

Hay dos partidos viables para la convocatoria de construcción:

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

Ambas son coincidencias exactas, pero #3 toma una referencia a un objeto calificado menos cv que #2 , por lo que tiene la mejor secuencia de conversión estándar y es la mejor función viable.

La solución aquí es restringir siempre estos constructores (por ejemplo, utilizando SFINAE):

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

El rasgo de tipo aquí es excluir de su consideración cualquier A o clase derivada pública y sin ambigüedad de A , lo que haría que este constructor no se formara correctamente en el ejemplo descrito anteriormente (y, por lo tanto, eliminado del conjunto de sobrecarga). Como resultado, se invoca el constructor de copia, que es lo que queríamos.

Pasos de resolución de sobrecarga

Los pasos de resolución de sobrecarga son:

  1. Encuentra funciones candidatas a través de búsqueda de nombre. Las llamadas no calificadas realizarán una búsqueda regular no calificada, así como una búsqueda dependiente del argumento (si corresponde).

  2. Filtrar el conjunto de funciones candidatas a un conjunto de funciones viables . Una función viable para la que existe una secuencia de conversión implícita entre los argumentos con los que se llama la función y los parámetros que toma la función.

    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. Elige el mejor candidato viable. Una función viable F1 es una función mejor que otra función viable F2 si la secuencia de conversión implícita para cada argumento en F1 no es peor que la secuencia de conversión implícita correspondiente en F2 , y ...:

    3.1. Para algunos argumentos, la secuencia de conversión implícita para ese argumento en F1 es una mejor secuencia de conversión que para ese argumento en F2 , o

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

    3.2. En una conversión definida por el usuario, la secuencia de conversión estándar desde el retorno de F1 al tipo de destino es una mejor secuencia de conversión que la del tipo de retorno de F2 , o

    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. En una vinculación de referencia directa, F1 tiene el mismo tipo de referencia que F2 , o

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

    3.4. F1 no es una especialización de plantilla de función, pero F2 es, o

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

    3.5. F1 y F2 son ambas especializaciones de plantilla de función, pero F1 es más especializado que F2 .

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

El ordenamiento aquí es significativo. La mejor verificación de la secuencia de conversión ocurre antes de la verificación de la plantilla frente a la no-plantilla. Esto conduce a un error común con la sobrecarga en la referencia de reenvío:

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

Si no hay un mejor candidato viable al final, la llamada es ambigua:

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

f(42); // error: ambiguous

Promociones y conversiones aritméticas.

Convertir un tipo entero en el tipo promovido correspondiente es mejor que convertirlo en otro tipo entero.

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

Promover un float para double es mejor que convertirlo en algún otro tipo de punto flotante.

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

Las conversiones aritméticas distintas de las promociones no son mejores ni peores que las otras.

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

Por lo tanto, para garantizar que no haya ambigüedad al llamar a una función f con argumentos integrales o de punto flotante de cualquier tipo estándar, se necesitan un total de ocho sobrecargas, por lo que para cada tipo de argumento posible, ya sea una sobrecarga Se seleccionará exactamente o la sobrecarga única con el tipo de argumento promovido.

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);

Sobrecarga dentro de una jerarquía de clases

Los siguientes ejemplos utilizarán esta jerarquía de clases:

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

La conversión del tipo de clase derivada al tipo de clase base se prefiere a las conversiones definidas por el usuario. Esto se aplica cuando se pasa por valor o por referencia, así como cuando se convierte puntero a derivado en puntero a base.

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

Una conversión de puntero de clase derivada a clase base también es mejor que la conversión a void* .

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

Si hay múltiples sobrecargas dentro de la misma cadena de herencia, se prefiere la sobrecarga de clase base más derivada. Esto se basa en un principio similar al envío virtual: se elige la implementación "más especializada". Sin embargo, la resolución de sobrecarga siempre se produce en el momento de la compilación y nunca se convertirá de forma implícita.

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

Para los punteros a los miembros, que son contravariantes con respecto a la clase, una regla similar se aplica en la dirección opuesta: se prefiere la clase derivada menos derivada.

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

Sobrecarga en constness y volatilidad.

Pasar un argumento de puntero a un parámetro T* , si es posible, es mejor que pasarlo a un parámetro 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

Del mismo modo, pasar un argumento a un parámetro T& , si es posible, es mejor que pasarlo a un parámetro const T& , incluso si ambos tienen rango de coincidencia exacta.

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

Esta regla se aplica también a las funciones miembro calificadas por const, donde es importante para permitir el acceso mutable a objetos no constantes y el acceso inmutable a objetos 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 misma manera, una sobrecarga volátil será menos preferida que una sobrecarga no volátil.

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow