c++ - funciones - ¿Debería esto compilar? Resolución de sobrecarga y conversiones implícitas.



ejemplos datos (2)

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.

https://ffff65535.com


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 como x.operator[](y) para un objeto de clase x de tipo T si T::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 el i -ésimo argumento en la lista al tipo del i -ésimo parámetro de la función viable F 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 viable F2 si, para todos los argumentos i , ICSi(F1) no es una secuencia de conversión peor que ICSi(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.


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 de foo::foo(const char*) ;
  • para el operator[](long, const char *) : (2-1) long(double(d)) través de Something::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.





overload-resolution