sobrecarga - conversion de tipos de datos en c++
¿Debería esto compilar? Resolución de sobrecarga y conversiones implícitas. (3)
Debería ser más fácil imaginar por qué la resolución de la sobrecarga es ambigua si se realiza paso a paso.
§13.5.5 [over.sub]
Por lo tanto, una expresión de subíndice
x[y]
se interpreta comox.operator[](y)
para un objeto de clasex
de tipoT
siT::operator[](T1)
existe y si el operador se selecciona como la función de mejor coincidencia Por el mecanismo de resolución de sobrecarga (13.3.3) .
Ahora, primero necesitamos un conjunto de sobrecarga. Se construye de acuerdo con §13.3.1
y contiene miembros y funciones no miembros. Vea esta respuesta mía para una explicación más detallada.
§13.3.1 [over.match.funcs]
p2 El conjunto de funciones candidatas puede contener funciones miembro y no miembro que se deben resolver en la misma lista de argumentos. De modo que las listas de parámetros y argumentos son comparables dentro de este conjunto heterogéneo, se considera que una función miembro tiene un parámetro adicional, denominado parámetro objeto implícito, que representa el objeto para el cual se ha llamado la función miembro . [...]
p3 De manera similar, cuando sea apropiado, el contexto puede construir una lista de argumentos que contenga un argumento de objeto implícito para denotar el objeto a operar.
// abstract overload set (return types omitted since irrelevant)
f1(Something&, foo const&); // linked to Something::operator[](foo const&)
f2(std::ptrdiff_t, char const*); // linked to operator[](std::ptrdiff_t, char const*)
f3(char const*, std::ptrdiff_t); // linked to operator[](char const*, std::ptrdiff_t)
Entonces, se construye una lista de argumentos:
// abstract argument list
(Something&, char const[3]) // ''Something&'' is the implied object argument
Y luego la lista de argumentos se prueba contra todos los miembros del conjunto de sobrecarga:
f1 -> identity match on argument 1, conversion required for argument 2
f2 -> conversion required for argument 1, conversion required for argument 2 (decay)
f3 -> argument 1 incompatible, argument 2 incompatible, discarded
Luego, como descubrimos que hay conversiones implícitas requeridas, echamos un vistazo a §13.3.3 [over.match.best] p1
:
Defina
ICSi(F)
como sigue:
- si
F
es una función miembro estática, [...]; de otra manera,- deje que
ICSi(F)
denote la secuencia de conversión implícita que convierte eli
-ésimo argumento en la lista al tipo deli
-ésimo parámetro de la función viableF
13.3.3.1 define las secuencias de conversión implícitas y 13.3.3.2 define lo que significa que una secuencia de conversión implícita sea una mejor secuencia de conversión o una secuencia de conversión peor que otra.
Ahora construyamos esas secuencias de conversión implícitas para f1
y f2
en el conjunto de sobrecarga ( §13.3.3.1
):
ICS1(f1): ''Something&'' -> ''Someting&'', standard conversion sequence
ICS2(f1): ''char const[3]'' -> ''foo const&'', user-defined conversion sequence
ICS1(f2): ''Something&'' -> ''std::ptrdiff_t'', user-defined conversion sequence
ICS2(f2): ''char const[3]'' -> ''char const*'', standard conversion sequence
§13.3.3.2 [over.ics.rank] p2
una secuencia de conversión estándar (13.3.3.1.1) es una mejor secuencia de conversión que una secuencia de conversión definida por el usuario o una secuencia de conversión de puntos suspensivos.
Entonces, ICS1(f1)
es mejor que ICS1(f2)
e ICS2(f1)
ICS1(f1)
es mejor que ICS1(f2)
ICS2(f2)
.
Por el contrario, ICS1(f2)
es peor que ICS1(f1)
e ICS2(f2)
es mejor que ICS2(f1)
.
§13.3.3 [over.match.best]
p1 (cont.) Dadas estas definiciones, una función viable
F1
se define como una mejor función que otra función viableF2
si, para todos los argumentosi
,ICSi(F1)
no es una secuencia de conversión peor queICSi(F2)
, y luego [ ...]p2 Si hay exactamente una función viable que es una función mejor que todas las demás funciones viables, entonces es la seleccionada por resolución de sobrecarga; De lo contrario la llamada está mal formada.
Bueno, f * ck. :) Como tal, Clang tiene razón al rechazar ese código.
Este ejemplo parece compilar con VC10 y gcc (aunque mi versión de gcc es muy antigua).
EDIT: R. Martinho Fernández probó esto en gcc 4.7 y el comportamiento sigue siendo el mismo.
struct Base
{
operator double() const { return 0.0; }
};
struct foo
{
foo(const char* c) {}
};
struct Something : public Base
{
void operator[](const foo& f) {}
};
int main()
{
Something d;
d["32"];
return 0;
}
Pero clang se queja:
test4.cpp:19:6: error: use of overloaded operator ''[]'' is ambiguous (with operand types ''Something'' and ''const char [3]'')
d["32"]
~^~~~~
test4.cpp:13:10: note: candidate function
void operator[](const foo& f) {}
^
test4.cpp:19:6: note: built-in candidate operator[](long, const char *)
d["32"]
^
test4.cpp:19:6: note: built-in candidate operator[](long, const restrict char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile restrict char *)
La resolución de sobrecarga está considerando dos funciones posibles al mirar esta expresión:
- llamando a Something :: operator [] (después de una conversión definida por el usuario)
- llamando al operador incorporado para const char * (piense "32" [d]) (después de que una conversión definida por el usuario y una conversión estándar doble a larga).
Si hubiera escrito d["32"]
como d.operator[]("32")
, entonces la resolución de sobrecarga ni siquiera se verá en la opción 2, y el clang también se compilará bien.
EDITAR: (aclaración de preguntas)
Esto parece ser un área complicada en la resolución de sobrecargas, y por eso apreciaría muchas respuestas que expliquen en detalle la resolución de sobrecargas en este caso, y cite el estándar (si hay alguna regla desconocida / avanzada que probablemente sea desconocida) .
Si el clang es correcto, también me interesa saber por qué los dos son ambiguos / uno no es preferido sobre otro. La respuesta probablemente tendría que explicar cómo la resolución de sobrecarga considera las conversiones implícitas (tanto las conversiones estándar como las definidas por el usuario) involucradas en los dos candidatos y por qué uno no es mejor que el otro.
Nota: si el operador double () se cambia a operador bool (), los tres (clang, vc, gcc) se negarán a compilar con un error ambiguo similar.
Parece que no hay duda de que tanto Something::operator[](const foo& f)
como el operator[](long, const char *)
integrado operator[](long, const char *)
son funciones candidatas viables (13.3.2) para la resolución de sobrecargas. Los tipos de argumentos reales son Something
y const char*
, y las secuencias de conversión implícitas (ICF) creo que son:
- for
Something::operator[](const foo& f)
: (1-1) conversión de identidad, y (1-2)foo("32")
través defoo::foo(const char*)
; - para el
operator[](long, const char *)
: (2-1)long(double(d))
través deSomething::operator double() const
(heredado de Base), y (2-2) conversión de identidad.
Ahora, si clasificamos estos ICF de acuerdo con (13.3.3.2), podemos ver que (1-1) es una conversión mejor que (2-1), y (1-2) es una conversión peor que (2-2) . Según la definición en (13.3.3),
una función viable F1 se define como una función mejor que otra función viable F2 si, para todos los argumentos i, ICSi (F1) no es una secuencia de conversión peor que ICSi (F2), ...
Por lo tanto, ninguna de las dos funciones candidatas consideradas es mejor que la otra, y por lo tanto la llamada está mal formada. Es decir, Clang parece ser correcto, y el código no debe compilarse.
Parecería que a partir de 13.6 en la especificación de C ++ 11, el clang es correcto aquí:
13.6 Operadores incorporados [over.built]
Las funciones de operador candidatas que representan los operadores integrados definidos en la Cláusula 5 se especifican en esta subcláusula. Estas funciones candidatas participan en el proceso de resolución de sobrecarga del operador como se describe en 13.3.1.2 y no se utilizan para ningún otro propósito. [Nota: debido a que los operadores incorporados solo toman operandos con un tipo que no es de clase, y la resolución de sobrecarga del operador ocurre solo cuando una expresión del operando originalmente tiene clase o tipo de enumeración, la resolución de sobrecarga del operador puede resolverse en un operador integrado solo cuando un operando tiene un tipo de clase que tiene una conversión definida por el usuario a un tipo que no es de clase apropiado para el operador, o cuando un operando tiene un tipo de enumeración que se puede convertir a un tipo apropiado para el operador. También tenga en cuenta que algunas de las funciones de operador candidatas dadas en esta subcláusula son más permisivas que los operadores incorporados. Como se describe en 13.3.1.2, después de seleccionar un operador integrado por resolución de sobrecarga, la expresión está sujeta a los requisitos para el operador incorporado que se indican en la Cláusula 5 y, por lo tanto, a cualquier restricción semántica adicional que se indique allí. Si hay un candidato escrito por el usuario con el mismo nombre y tipos de parámetros que una función de operador candidato incorporado, la función de operador integrada está oculta y no se incluye en el conjunto de funciones candidatas. - nota final]
:
Para cada tipo de objeto cv calificado o cv no calificado, existen funciones de operador candidatas del formulario
T y operador [] (T *, std :: ptrdiff_t);
T y operador [] (std :: ptrdiff_t, T *);
editar
Una vez que supera cuáles son las funciones del operador, esto se convierte en una resolución de sobrecarga estándar, tal como se describe en la sección 13.3 de la norma: alrededor de 10 páginas de detalles, pero lo esencial es que para que una llamada a la función no sea ambigua, es necesario sea una función única que sea al menos tan compatible como todas las funciones posibles y viables en cada argumento, y una mejor coincidencia que las demás en al menos un argumento. Hay una gran cantidad de detalles de especificaciones sobre qué significa exactamente ''mejor'', pero se reduce a (en este caso) una coincidencia que no requiera de un operador de conversión definido por el usuario o un constructor de objetos es mejor que una que sí lo haga.
Así que en este caso, hay dos coincidencias viables:
void Something::operator[](const foo& f)
operator[](long, const char *)
El primero es una mejor coincidencia para el primer argumento, mientras que el segundo es una mejor coincidencia para el segundo argumento. Entonces, a menos que haya alguna otra función que sea mejor que ambas, es ambigua.
Este último punto es una solución posible - agregue:
void operator[](const char *a) { return (*this)[foo(a)]; }
a clase algo