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.
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
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:
- si
f
la expr del argumento es lvalue, Bothx
T
valorarán el tipo de referencia - 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
ostring&
, para la función forward_Tp
serástring&
,__t
serástring & &
is igual astring&
,_Tp&&
serástring& &&
igual astring&
. (reference collapsing) - if pass function f rvalue of type es
string
ostring&&
, 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.