2011-12-01 14 views
18

¿Por qué el siguiente código válido:¿Por qué se debe modelar una función de reenvío perfecta?

template<typename T1> 
void foo(T1 &&arg) { bar(std::forward<T1>(arg)); } 

std::string str = "Hello World"; 
foo(str); // Valid even though str is an lvalue 
foo(std::string("Hello World")); // Valid because literal is rvalue 

Pero no:

void foo(std::string &&arg) { bar(std::forward<std::string>(arg)); } 

std::string str = "Hello World"; 
foo(str); // Invalid, str is not convertible to an rvalue 
foo(std::string("Hello World")); // Valid 

¿Por qué el valor-I en el ejemplo 2 se resuelven de la misma manera que lo hace en el ejemplo 1?

Además, ¿por qué la norma considera importante proporcionar el tipo de argumento en std :: forward frente a simple deducción? Simplemente llamar hacia adelante muestra intención, independientemente del tipo.

Si esto no es una cosa estándar y solo mi compilador, estoy usando msvc10, lo que explicaría el mal soporte de C++ 11.

Gracias

Edición 1: Se ha cambiado el literal "Hola Mundo" a ser std :: string ("Hola mundo") para hacer una rvalue.

+0

¿Qué sucede en la barra? Compilar no significa que esté funcionando necesariamente. Creo que debería ser 'void foo (T1 & arg)' y 'void foo (std :: string & arg)' respectivamente. – AJG85

+1

'" Hello World "' no es un valor r, es un valor l con el tipo 'const char [12]'. – GManNickG

+0

@ AJG85 Lo que sucede en la barra no es importante. && significa referencia de valor r. – Mranz

Respuesta

15

Antes que nada, read this para tener una idea completa del reenvío. (Sí, estoy delegando la mayor parte de esta respuesta en otro lugar.)

En resumen, el reenvío significa que los valores l permanecen lvalues ​​y los valores rvalues ​​permanecen. No puede hacer eso con un solo tipo, por lo que necesita dos. Por lo tanto, para cada argumento reenviado, necesita dos versiones para ese argumento, que requiere 2 N combinaciones totales para la función. Usted podría codificar todas las combinaciones de la función, pero si usa plantillas, esas diversas combinaciones se generarán para usted según sea necesario.


Si usted está tratando de optimizar las copias y se mueve, como en:

struct foo 
{ 
    foo(const T& pX, const U& pY, const V& pZ) : 
    x(pX), 
    y(pY), 
    z(pZ) 
    {} 

    foo(T&& pX, const U& pY, const V& pZ) : 
    x(std::move(pX)), 
    y(pY), 
    z(pZ) 
    {} 

    // etc.? :(

    T x; 
    U y; 
    V z; 
}; 

Entonces usted debe parar y hacerlo de esta manera:

struct foo 
{ 
    // these are either copy-constructed or move-constructed, 
    // but after that they're all yours to move to wherever 
    // (that is, either: copy->move, or move->move) 
    foo(T pX, U pY, V pZ) : 
    x(std::move(pX)), 
    y(std::move(pY)), 
    z(std::move(pZ)) 
    {} 

    T x; 
    U y; 
    V z; 
}; 

Sólo se necesita una constructor. Directriz: si necesita su propia copia de los datos, haga esa copia en la lista de parámetros; esto permite la decisión de copiar o pasar a la persona que llama y al compilador.

+1

Así que la razón por la que el primer ejemplo funciona es porque T1 realmente se resuelve con el equivalente de 'void foo (const std :: string & && arg)' que se reduce a 'void foo (const std :: string & arg)' usando la referencia reglas de deducción? ¿Falla en el ejemplo 2 porque no hay una sobrecarga lvalue para la cadena? ¿Existen algunas mejores prácticas para definir la función de la plantilla, de modo que al menos sea relativamente obvia cuál debería ser el tipo? Idealmente, estoy buscando una buena manera de ser explícito con el tipo mientras evito sobrecargas de 2^N. – Mranz

+1

@Mranz: Precisamente. Bueno, ¿a qué vas? El reenvío y las plantillas están destinados a ser utilizados para el reenvío, realmente no debería necesitar saber los tipos. – GManNickG

+0

Digamos que tengo un constructor que toma en un vector como 'Class (.. some args ..., vector items)'. Idealmente, me gustaría permitir que ese argumento evite la copia usando la semántica de movimiento o ser un valor r. Para respaldar eso, tendría que diseñar elementos o crear una sobrecarga de valor. Si lo explico, el tipo real se pierde en la definición, y los consumidores de mi clase deben deducir el argumento real leyendo el archivo de encabezado o algún comentario. – Mranz

Cuestiones relacionadas