C++
Resolución de sobrecarga
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 deClass
. - 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 deClass
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 ar
. 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 deClass
como candidatos.
- Para la inicialización directa no de lista (
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:
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).
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
Elige el mejor candidato viable. Una función viable
F1
es una función mejor que otra función viableF2
si la secuencia de conversión implícita para cada argumento enF1
no es peor que la secuencia de conversión implícita correspondiente enF2
, 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 enF2
, ovoid 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 deF2
, ostruct 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 queF2
, ostruct 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, peroF2
es, otemplate <class T> void f(T ); // #1 void f(int ); // #2 f(42); // calls #2, the non-template
3.5.
F1
yF2
son ambas especializaciones de plantilla de función, peroF1
es más especializado queF2
.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