Reenvío de valores devueltos. ¿Es necesario std:: forward?


Estoy escribiendo biblioteca que envuelve una gran cantidad de funciones y métodos de otra biblioteca. Para evitar hacer frente a los valores de retorno estoy aplicando std::forward de la siguiente manera:

template<class T>
T&& wrapper(T&& t) { 
   f(t);  // t passed as lvalue  
   return std::forward<T>(t);
}

f devuelve void y toma T&& (o sobrecargado de valor). Wrapper siempre devuelve el parámetro de wrappers y el valor devuelto debe preservar la valuness del argumento. ¿Realmente necesito usar std::forward en return? ¿RVO lo hace superfluo? ¿El hecho de que sea una referencia (R o L) lo hace superfluo? Es necesario if return no es la última sentencia de función (dentro de algún if)?

Es discutible si wrapper() debe devolver void o T&&, porque el llamante tiene acceso al valor evaluado a través de arg (que es referencia, R o L). Pero en mi caso necesito devolver valor para que wrapper() pueda ser usado en expresiones.

Podría ser irrelevante para la pregunta, pero se sabe que las funciones f no a robar t, así 1er uso de {[2] {} en[15]} es superfluo y fue eliminado por me.

He escrito una pequeña prueba: https://gist.github.com/3910503

Prueba muestra, que el retorno no premeditado T - does crea copia extra en gcc48 y clang32 con-O3 (RVO no se activa).

Además, no pude obtener mal comportamiento de UB en:

auto&& tmp = wrapper(42); 

No prueba nada de causa porque es comportamiento indefinido (si es UB).

Author: Martijn Pieters, 2012-10-18

3 answers

En el caso de que sepa que t no estará en un estado moved-from después de la llamada a f, sus dos opciones algo sensatas son:

  • Devuelve std::forward<T>(t) con el tipo T&&, que evita cualquier construcción pero permite escribir, por ejemplo, auto&& ref = wrapper(42);, que deja ref una referencia colgando

  • Devuelve std::forward<T>(t) con el tipo T, que en el peor de los casos solicita una construcción de movimiento cuando el parámetro es un rvalue this esto evita el problema anterior para prvalues pero potencialmente roba de xvalues

En todos los casos necesitas std::forward. Copy elision no se considera porque t es siempre una referencia.

 9
Author: Luc Danton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-18 07:19:23

Dependiendo de lo que esta función se pasa, resulta en un comportamiento indefinido! Más precisamente, si pasa un no-lvalue, es decir, un rvalue, a esta función, el valor referenciado por la referencia devuelta será obsoleto.

También T&& no es una "referencia universal" aunque el efecto es algo así como una referencia universal en que T se puede deducir como T& o T const&. El caso problemático es cuando se deduce como T: los argumentos se pasan como temporales y muere después de que la función regrese pero antes de que cualquier cosa pueda hacerse con una referencia a ella.

El uso de std::forward<T>(x) está limitado a, bueno, reenviar objetos cuando se llama a otra función: lo que entró como temporal se parece a un lvalue dentro de la función. Usando std::forward<T>(x) permite que x se vea como un temporal si vino como uno - y, por lo tanto, permite moverse desde x al crear el argumento de la función llamada.

Cuando devuelve un objeto desde una función, hay algunos escenarios podría querer cuidar de pero ninguno de ellos implica std::forward():

  • Si el tipo es realmente una referencia, ya sea const o no-const, no desea hacer nada con el objeto y simplemente devolver la referencia.
  • Si todas las sentencias return usan la misma variable o todas están usando una elisión temporal, copy/move se puede usar y se usará en compiladores decentes. Sin embargo, dado que la elisión copiar/mover es una optimización, no sucede necesariamente.
  • Si siempre es lo mismo se devuelve una variable local o temporal desde la que se puede mover si hay un constructor move, de lo contrario se copiará el objeto.
  • Cuando se devuelven diferentes variables o cuando el retorno implica una expresión, las referencias pueden ser devueltas, pero la elisión copiar/mover no funcionará, ni será directamente posible moverse desde el resultado si no es temporal. En estos casos necesita usar std::move() para permitir moverse desde el objeto local.

En la mayoría de estos casos el tipo producido es T y debe devolver T en lugar de T&&. Si T es un tipo lvalue, el resultado puede no ser un tipo lvalue, sin embargo, y puede ser necesario eliminar la calificación de referencia del tipo devuelto. En el escenario que preguntó específicamente sobre el tipo T funciona.

 5
Author: Dietmar Kühl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-20 13:43:42

No no necesita usar std::forward mejor no devuelva la referencia de valor r en absoluto porque puede evitar la optimización de NRVO. Puedes leer más sobre la semántica del movimiento en este artículo: Article

 3
Author: Denis Ermolin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-18 06:34:53