verdaderos validos tipos premisas inductivo falsas falsa ejemplos conclusiones conclusion argumentos argumento c++ templates c++11 rvalue-reference perfect-forwarding

c++ - validos - premisas falsas y conclusion falsa ejemplos



¿Cómo std:: forward recibe el argumento correcto? (3)

¿Por qué la llamada no se vincula a la sobrecarga de std::forward que toma un lvalue?

Hace exactamente eso, pero std::forward no deduce su argumento de plantilla, usted le dice de qué tipo es, y ahí es donde ocurre la magia. Está pasando un prvalue a f() así que f() deduce [T = int] . Luego llama a la sobrecarga lvalue de forward , y debido a la referencia que colapsa tanto el tipo de retorno como static_cast<T&&> que ocurre dentro de forward será de tipo int&& , llamando así a la sobrecarga void g(int&&) .

Si pasara un lvalue a f()

int x = 0; f(x);

f() deduce [T = int&] , nuevamente se llama a la misma sobrecarga lvalue de forward , pero esta vez el tipo de retorno y static_cast<T&&> son int& , de nuevo, debido a las reglas de static_cast<T&&> de referencia. Esto llamará a la sobrecarga void g(int&) lugar.

Demo en vivo

Howard ya tiene una buena respuesta para su pregunta acerca de por qué se requiere la sobrecarga rvalue de forward , pero agregaré un ejemplo artificial que muestra las dos versiones en acción.

La idea básica detrás de las dos sobrecargas es que se invocará la sobrecarga rvalue en los casos en que el resultado de la expresión que pase a forward arroje un valor r (prvalue o xvalue).

Supongamos que tiene un tipo foo que tiene un par de sobrecargas de función de miembro get() calificadas por ref. El que tiene el calificador && devuelve un int mientras que el otro devuelve int& .

struct foo { int i = 42; int get() && { return i; } int& get() & { return i; } };

Y digamos que f() invoca la función miembro get() en lo que se le pasó, y lo reenvía a g()

template<class T> auto f(T&& t) { std::cout << __PRETTY_FUNCTION__ << ''/n''; g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get())); } foo foo1; f(foo1); // calls lvalue overload of forward for both calls to forward f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t) // but calls rvalue overload for outer call to forward

Demo en vivo

Considerar:

void g(int&); void g(int&&); template<class T> void f(T&& x) { g(std::forward<T>(x)); } int main() { f(10); }

Como la expresión-id x es un valor l, y std::forward tiene sobrecargas para los valores l y r, ¿por qué la llamada no se vincula a la sobrecarga de std::forward que toma un valor l?

template<class T> constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;


Se une a la sobrecarga de std::forward tomando un lvalue:

template <class T> constexpr T&& forward(remove_reference_t<T>& t) noexcept;

Se une con T == int . Esta función se especifica para regresar:

static_cast<T&&>(t)

Porque el T en f dedujo a int . Entonces esta sobrecarga arroja lvalue int a xvalue con:

static_cast<int&&>(t)

Llamando así a la sobrecarga g(int&&) .

En resumen, la sobrecarga lvalue de std::forward puede convertir su argumento en lvalue o rvalue, según el tipo de T que se invoque.

La sobrecarga rvalue de std::forward solo puede convertirse a rvalue. Si intentas llamar a esa sobrecarga y convertirla a lvalue, el programa está mal formado (se requiere un error en tiempo de compilación).

Así que sobrecarga 1:

template <class T> constexpr T&& forward(remove_reference_t<T>& t) noexcept;

atrapa lvalues.

Sobrecarga 2:

template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;

capta valores (que son xvalores y valores primos).

La sobrecarga 1 puede convertir su argumento lvalue en lvalue o xvalue (este último se interpretará como un valor r para fines de resolución de sobrecarga).

Overload 2 puede convertir su argumento rvalue solo en un valor x (que se interpretará como un valor r para fines de resolución de sobrecarga).

La sobrecarga 2 es para el caso etiquetado como "B. Debe reenviar un valor r como valor r" en N2951 . En pocas palabras, este caso permite:

std::forward<T>(u.get());

donde no está seguro si u.get() devuelve un valor lvalor o rvalue, pero en cualquier caso si T no es un tipo de referencia lvalue, desea mover el valor devuelto. Pero no usa std::move porque si T es un tipo de referencia lvalue, no desea moverse desde la devolución.

Sé que esto suena un poco artificial. Sin embargo, N2951 tuvo problemas importantes para configurar casos de uso motivadores de cómo std::forward debería comportarse con todas las combinaciones del parámetro de plantilla suministrado explícitamente, y la categoría de expresión suministrada implícitamente del parámetro ordinario.

No es una lectura fácil, pero la razón fundamental para cada combinación de plantilla y parámetros comunes para std::forward es en N2951 . En el momento esto era controvertido en el comité, y no es fácil de vender.

La forma final de std::forward no es exactamente lo que N2951 propuso. Sin embargo , pasa las seis pruebas presentadas en N2951 .


¿Cómo std :: forward recibe el argumento correcto?

Para un reenvío perfecto, como tu código:

template<class T> void f(T&& x) { g(std::forward<T>(x)); }

Tenga en cuenta esto: std :: forward requiere un argumento de función y un argumento de tipo de plantilla. El parámetro de la plantilla T codificará si el argumento pasado a param fue un lvalue o un valor r y luego reenviarlo. En este caso, no importa x refiera qué, x sí mismo es un valor l, la sobrecarga de elección 1, y x es parámetros de referencia de reenvío (universales). Las reglas son las siguientes:

  1. si f la expr del argumento es lvalue, Both x T valorarán el tipo de referencia
  2. si f el argumento expr es rvalue, T será de tipo no referencia.

Por ejemplo, use el código fuente de gcc:

template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } //overload 1 template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); }// overload 2

  • if pass function f lvalue del tipo es string o string& , para la función forward _Tp será string& , __t será string & & is igual a string& , _Tp&& será string& && igual a string& . (reference collapsing)
  • if pass function f rvalue of type es string o string&& , para la función forward _Tp será string , __t será string& , _Tp&& será string&& .

¿Qué hace la función?

  • si rvalue a rvalue (Sobrecarga 2) o lvalue a lvalue (Sobrecarga 1), no hay nada que hacer, simplemente devuelva.
  • Si no tiene cuidado u otra cosa, lleve el valor r a lvalue, le da un error de compilación por static_assert . (Sobrecarga 2)
  • Si haces lvalue a rvalue (Sobrecarga 1), es lo mismo con std :: move, pero no conveniencia.

Así que, como dice Scott Meyers:

Dado que tanto std :: move como std :: forward se reducen a lanzamientos, la única diferencia es que std :: move siempre arroja, mientras que std :: forward solo lo hace a veces, puede preguntar si podemos prescindir de std :: move y solo use std :: forward en todas partes. Desde una perspectiva puramente técnica, la respuesta es sí: std :: forward puede hacerlo todo. std :: move no es necesario. Por supuesto, ninguna función es realmente necesaria, porque podríamos escribir moldes en todas partes, pero espero que estemos de acuerdo en que eso sería, bueno, asqueroso.

¿Para qué es la sobrecarga rvalue?

No estoy seguro, debe usarse donde realmente lo necesite. Como Howard Hinnant dice:

La sobrecarga 2 es para el caso etiquetado como "B. Debe reenviar un valor r como valor r" en N2951. En pocas palabras, este caso permite:

std::forward<T>(u.get());

donde no está seguro si u.get () devuelve un valor lvalor o rvalue, pero en cualquier caso si T no es un tipo de referencia lvalue, desea mover el valor devuelto. Pero no usa std :: move porque si T es un tipo de referencia lvalue, no desea moverse desde la devolución.

En una palabra, nos permite usar el tipo de T para decidir si mover o no.