¿Por qué se elige esta sobrecarga de un operador de conversión?

27

Considere el siguiente código .

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

Aquí el segundo operador de conversión es elegido por la resolución de sobrecarga. ¿Por qué?

Por lo que yo entiendo, los dos operadores se deducen operator int &&() consty operator int &() constrespectivamente. Ambos están en el conjunto de funciones viables. Leer a través de [over.match.best] no me ayudó a entender por qué este último es mejor.

¿Por qué la última función es mejor que la primera?

avakar
fuente
Soy un gato simple y diría que debe ser el segundo una vez más, la introducción del otro habría sido un cambio radical en C ++ 11. Estará en el estándar en alguna parte. Buena pregunta, sin embargo, tener un voto a favor. (Atención expertos: si este comentario es una tontería, ¡díganmelo!)
Bathsheba
3
FWIW, template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;consigue que llame al primero.
NathanOliver
55
@LightnessRaceswithMonica Los operadores de conversión lo permiten, de lo contrario no habría forma de tener múltiples operadores de conversión.
NathanOliver
1
@Bathsheba No creo que esa sea la razón. Eso es como decir que los constructores de movimiento nunca pueden seleccionarse por resolución de sobrecarga porque sería un cambio radical. Si escribe un constructor de movimiento, está optando por que su código esté "roto" por esa definición.
Brian
1
@LightnessRaceswithMonica La forma en que lo mantengo en mi cabeza es que trato el tipo de retorno como el nombre de la función. Diferentes tipos equivalen a diferentes nombres equivale a éxito :)
NathanOliver

Respuestas:

13

Se T&prefiere el operador de conversión que regresa porque es más especializado que el operador de conversión que regresa T&&.

Ver C ++ 17 [temp.deduct.partial] / (3.2):

En el contexto de una llamada a una función de conversión, se utilizan los tipos de retorno de las plantillas de la función de conversión.

y / 9:

Si, para un tipo dado, la deducción tiene éxito en ambas direcciones (es decir, los tipos son idénticos después de las transformaciones anteriores) y ambos Py Aeran tipos de referencia (antes de ser reemplazados por el tipo mencionado anteriormente): - si el tipo de la plantilla de argumento era una referencia de valor y el tipo de la plantilla de parámetros no lo era, el tipo de parámetro no se considera al menos tan especializado como el tipo de argumento; ...

Brian
fuente
12

Los operadores de conversión de valor de retorno deducidos son un poco extraños. Pero la idea central es que actúa como un parámetro de función para elegir cuál se usa.

Y al decidir entre T&&y T&las T&victorias en las reglas de resolución de sobrecarga. Esto es para permitir:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

trabajar. T&&puede coincidir con un valor, pero cuando están disponibles las sobrecargas de referencia universal y de valor, se prefiere el valor.

El conjunto correcto de operadores de conversión es probablemente:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

o incluso

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

para evitar que una extensión fallida de por vida te muerda.

3 Los tipos utilizados para determinar el orden dependen del contexto en el que se realiza el orden parcial:

[RECORTE]

(3.2) En el contexto de una llamada a una función de conversión, se utilizan los tipos de retorno de las plantillas de la función de conversión.

Que luego termina dependiendo de las reglas "más especializadas" al elegir sobrecargas:

(9.1) si el tipo de la plantilla de argumento era una referencia de valor y el tipo de la plantilla de parámetro no lo era, el tipo de parámetro no se considera al menos tan especializado como el tipo de argumento; de otra manera,

Por operator T&&lo tanto, no es al menos tan especializado como operator T&, mientras que ningún estado de regla operator T&no es al menos tan especializado como operator T&&, por lo que operator T&es más especializado que operator T&&.

Más plantillas especializadas ganan resolución de sobrecarga por menos, todo lo demás es igual.

Yakk - Adam Nevraumont
fuente
Alguien agregó la etiqueta de idioma-abogado mientras respondía; Simplemente podría borrar. Tengo un vago recuerdo de leer el texto que dice que se trata como un parámetro para elegir cuál se llama, y ​​el texto que habla sobre cómo se trata &&vs &cuando se seleccionan las sobrecargas de plantillas, pero extraer el texto exacto llevaría un tiempo .
Yakk - Adam Nevraumont
4

Estamos tratando de inicializar un intde un any. El proceso para eso es:

  1. Averigua cómo podemos hacer eso. Es decir, determinar todos nuestros candidatos. Estos provienen de las funciones de conversión no explícitas a las que se puede convertir intmediante una secuencia de conversión estándar ( [over.match.conv] ). La sección contiene esta frase:

    Una llamada a una función de conversión que devuelve "referencia a X" es un valor de tipo gl Xy, por lo tanto, se considera que dicha función de conversión rinde Xpara este proceso de selección de funciones candidatas.

  2. Elige el mejor candidato.

Después del paso 1, tenemos dos candidatos. operator int&() consty operator int&&() const, los cuales se considera que rinden intcon el propósito de seleccionar funciones candidatas. ¿Cuál es el mejor candidato que rinde int?

Tenemos un desempate que prefiere las referencias de valor a las referencias de valor ( [over.ics.rank] /3.2.3 ). Sin embargo, en realidad no estamos vinculando una referencia aquí, y el ejemplo allí está algo invertido; esto es para el caso en el que el parámetro es una referencia de lvalue vs rvalue.

Si eso no se aplica, entonces pasamos al desempate [over.match.best] /2.5 de preferir la plantilla de función más especializada.

En términos generales, la regla general es que la conversión más específica es la mejor coincidencia. La función de conversión de referencia lvalue es más específica que la función de conversión de referencia de reenvío, por lo que es preferible. No hay nada acerca de la intinicialización que requiera un valor r (si hubiéramos estado inicializando un valor int&&, entonces operator T&() constno habría sido un candidato).

Barry
fuente
3.2.3 en realidad dice T&&victorias ¿no? Ah, pero no tenemos un valor al que depender. O nosotros? Al elegir a nuestros candidatos, algunas palabras deben haber definido cómo llegamos int&y int&&, ¿fue vinculante?
Yakk - Adam Nevraumont
@ Yakk-AdamNevraumont No, ese prefiere las referencias de valor a las referencias de valor. Creo que esa es probablemente la sección incorrecta de todos modos, y el desempate "más especializado" es el correcto.
Barry