2010-05-01 12 views
13

(pedí una variación de esta pregunta en comp.std.C++ pero no conseguir una respuesta.)C++ 0x rvalue referencias temporales y

¿Por qué la llamada a f(arg) en este código de llamada del const Sobrecarga de ref de f?

void f(const std::string &); //less efficient 
void f(std::string &&); //more efficient 

void g(const char * arg) 
{ 
    f(arg); 
} 

Mi intuición dice que la sobrecarga f(string &&) debe ser elegido, porque arg necesita ser convertido a un temporal no importa qué, y coincide con el temporal de la referencia rvalue mejor que la referencia lvalue.

Esto no es lo que sucede en GCC y MSVC (edit: Gracias Sumant: no ocurre en GCC 4.3-4.5). En al menos G ++ y MSVC, cualquier lvalue no se enlaza con un argumento de referencia rvalue, aunque haya un temporal intermedio creado. De hecho, si la sobrecarga const ref no está presente, los compiladores diagnostican un error. Sin embargo, escribiendo f(arg + 0) o f(std::string(arg))elige y elige la sobrecarga de referencia de valor acumulado como es de esperar.

Según mi lectura del estándar C++ 0x, parece que la conversión implícita de un const char * a una cadena debe considerarse al considerar si f(string &&) es viable, al igual que al pasar un argumento const lvalue ref. La sección 13.3 (resolución de sobrecarga) no diferencia entre referencias rvalue y referencias de referencias en demasiados lugares. Además, parece que la regla que impide que lvalues ​​se vincule con las referencias rvalue (13.3.3.1.4/3) no debería aplicarse si hay un intermedio temporal; después de todo, es perfectamente seguro pasar del temporal.

es la siguiente:

  1. Me mala lectura/malinterpretar la norma, donde el comportamiento implementado es el comportamiento previsto, y hay una buena razón por la que mi ejemplo debería comportarse de la manera que lo hace?
  2. ¿Algún error cometido por los fabricantes de compiladores? ¿O un error basado en estrategias comunes de implementación? O un error en, por ejemplo, GCC (donde se implementó por primera vez esta regla de enlace de referencia lvalue/rvalue), que otros proveedores copiaron?
  3. ¿Un defecto en el estándar, o una consecuencia involuntaria, o algo que debería aclararse?

EDIT: Tengo una pregunta de seguimiento que se relaciona: C++0x rvalue references - lvalues-rvalue binding

+0

En la situación dada, ¿por qué la sobrecarga sería más rápida? Un objeto de cadena debe ser creado de una forma u otra y una referencia vinculada a él. – UncleBens

+0

@UncleBens: supongamos que la función almacena su argumento en alguna variable global/variable miembro: si se sabe que el argumento es movible, puede moverse al destino en lugar de copiarse. – Doug

Respuesta

8

GCC lo está haciendo mal según el FCD. La FCD dice en 8.5.3 acerca del enlace

  • Si la referencia es una referencia de valor-I y la expresión de inicialización es un [lvalue/tipo de clase] referencia ...
  • De lo contrario, la referencia será una referencia a un valor-I el tipo const no volátil (es decir, cv1 debe ser const), o la referencia debe ser una referencia rvalue y la expresión del inicializador debe ser un valor r o tener un tipo de función.

su caso por la llamada a la std::string && no coincide con ninguno de ellos, porque el inicializador es un lvalue. No llega al lugar para crear un valor r temporal, porque esa bala toplevel ya requiere un valor r.

Ahora, la resolución de sobrecarga no usa directamente el enlace de referencia para ver si existe una secuencia de conversión implícita. En lugar de ello, se dice en 13.3.3.1.4/2

Cuando un parámetro de tipo de referencia no se une directamente a una expresión argumento, la secuencia de conversión es la requerida para convertir la expresión argumento para el tipo subyacente de la referencia de acuerdo con 13.3. 3.1.

Por lo tanto, la resolución de sobrecarga se da cuenta de un ganador, a pesar de que ese ganador en realidad no puede vincularse a ese argumento. Por ejemplo:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); 
void f(B); 
int main() { A a; f(a.bits); } 

vinculante en 8.5 Referencia prohíbe campos de bits para unirse a lValue referencias. Pero la resolución de sobrecarga dice que la secuencia de conversión es la que se convierte a int, teniendo éxito a pesar de que cuando la llamada se realiza más tarde, la llamada no está bien formada. Por lo tanto, mi ejemplo de bitfields está mal formado. Si fuera a elegir la versión B, habría tenido éxito, pero necesitaba una conversión definida por el usuario.

Sin embargo,, existen dos excepciones para esa regla. Estos se

Excepto para un parámetro de objeto implícito, para el que ver 13.3.1, una secuencia estándar de conversión no se puede formar si se requiere unión a una referencia lvalue a la no-const a un valor p o de unión a una referencia rvalue a una lvalue

Por lo tanto, la siguiente llamada es válida:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); /* binding an lvalue ref to non-const to rvalue! */ 
void f(B); 
int main() { A a; f(1); } 

Y así, el ejemplo llama a la versión const T&

void f(const std::string &); 
void f(std::string &&); // would bind to lvalue! 

void g(const char * arg) { f(arg); } 

Sin embargo, si usted dice f(arg + 0), se crea un valor de lado derecho, y por lo tanto, la segunda función es viable.

+0

Ah, no leí demasiado el 8.5.3, gracias. Parece que las reglas para las referencias de enlace son innecesariamente estrictas: no veo que haya ningún caso de uso convincente para rechazar dicho código, pero puede (inesperadamente, IMO) provocar copias no intencionadas, por ejemplo, si llamaste al vector :: push_back con un argumento const char * lvalue. – Doug

+1

Una cosa más: la prohibición de aceptar cambios temporales (para mí) es específicamente para que no pierda accidentalmente ninguna información "adicional" que una función pueda estar devolviendo a través de esa referencia. Pero esta consideración no se aplica a una referencia rvalue, ya sea const o non-const, conceptualmente "no es un valor útil" después de la llamada. Finalmente, el hecho de que f (arg) no se permite pero f (arg + 0) está permitido, o que, p. stringvec.push_back (string (charptr)) es * más eficiente * que stringvec.push_back (charptr) es muy poco intuitivo para mí. – Doug

+0

Esta respuesta no está actualizada, ¿o sí? El borrador actual requiere que se tome la segunda sobrecarga sin ningún error. – sellibitze

1

Una gran cantidad de cosas en el actual borrador de la norma necesita alguna aclaración, si me preguntas. Y los compiladores todavía están en desarrollo, por lo que es difícil confiar en su ayuda.

Parece bastante claro que su intuición es correcta ... se supone que los temporales de cualquier tipo se unen a las referencias de valor real. Por ejemplo, §3.10, la nueva sección "taxonomía" define categóricamente los temporales como valores r.

El problema puede ser que la especificación del argumento RR es insuficiente para invocar la creación de un temporal. §5.2.2/5: "Cuando un parámetro es de tipo constante, se introduce un objeto temporal si es necesario". Eso suena sospechosamente exclusivo.

Parece deslizarse a través de las grietas de nuevo en §13.3.3.1/6: (énfasis mío)

Cuando el tipo de parámetro no es una referencia, los implícitos modelos de secuencia de conversión de una copia-inicialización del parámetro de la expresión argumento. La secuencia de conversión implícita es la requerida para convertir la expresión del argumento a un valor pr del tipo del parámetro.

Tenga en cuenta que la inicialización de copia string &&rr = "hello"; funciona bien en GCC.

EDIT: En realidad, el problema no existe en mi versión de GCC. Todavía estoy tratando de descubrir cómo la segunda conversión estándar de la secuencia de conversión definida por el usuario se relaciona con la formación de una referencia de valor real. (¿Es la formación de RR una conversión en absoluto O es dictado por cositas dispersos como 5.2.2/5??)

+0

Gracias - Me perdí esas cláusulas que citó. Ellos parecen confundir las cosas. Además, la razón por la que no pasé un literal de cadena es porque MSVC parece tratarlos como valores l, pero GCC no lo hace, es bastante extraño. – Doug

2

No vi el comportamiento mencionado por Doug en g ++. g ++ 4.5 y 4.4.3 llaman a f(string &&) como se esperaba pero VS2010 llama a f(const string &). ¿Qué versión de g ++ estás usando?

+0

+1, ¡ojalá hubiera verificado el problema antes de entrar! – Potatoswatter

+0

Volví a verificar en G ++ y tienes razón, no sucede. ¡Estoy seguro de que lo revisé allí antes! Parece que tal vez el problema es solo en MSVC, o no está claro en el estándar. – Doug

0

No sé si eso ha cambiado en las últimas versiones de la norma, pero solía decir algo así como "en caso de duda, no utilice la referencia rvalue". Probablemente por razones de compatibilidad.

Si desea la semántica de movimiento, use f(std::move(arg)), que funciona con ambos compiladores.

6

Era un defecto en el borrador estándar que leyó. Este defecto surgió como un efecto secundario de algunas ediciones ansiosas por no permitir el enlace de las referencias de valor a los valores l por motivos de seguridad.

Su intuición es correcta. Por supuesto, no hay daño al permitir que una referencia de valor real haga referencia a un temporal no nombrado incluso si el inicializador fue una expresión lvalue. Después de todo, esto es para lo que son las referencias rvalue. El problema que observó se ha solucionado el año pasado. La próxima norma exigirá que la segunda sobrecarga se recoja en su ejemplo, donde la referencia de valor de referencia se referirá a algún objeto de cadena temporal.

La solución regla lo hizo en el proyecto de n3225.pdf (2010-11-27):

  • [...]
  • De lo contrario, la referencia será una referencia a un valor-I el tipo const no volátil (es decir, cv1 debe ser const), o la referencia debe ser una referencia rvalue y la expresión del inicializador debe ser un valor r o tener un tipo de función . [...]
    • [...]
    • lo contrario, un temporal de la [...] se crea [...]
 double&& rrd3 = i; // rrd3 refers to temporary with value 2.0 

Pero Parece que N3225 se olvidó de decir qué es i en este ejemplo.El último borrador N3290 contiene estos ejemplos:

 double d2 = 1.0; 
     double&& rrd2 = d2; // error: copying lvalue of related type 
     int i3 = 2; 
     double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0 

Debido a que su versión MSVC fue puesto en libertad antes de que este problema fue resuelto, todavía maneja referencias rvalue de acuerdo con las viejas reglas. Se espera que la próxima versión de MSVC implemente las nuevas reglas de referencia rvalue (denominadas "referencias de rvalue 2.1" por los desarrolladores de MSVC) see link.

Cuestiones relacionadas