descargar - c++14
La implementación de std:: forward (3)
El primer caso, como dijo Sebastian Redl, siempre le dará una referencia de valor. La razón es que una referencia rvalue en el parámetro se pasaría como una referencia lvalue, y el tipo de parámetro T&&
es una referencia universal en lugar de una referencia rvalue.
En realidad, si el primer caso es correcto, ni siquiera necesitamos forward
. Aquí hay un experimento para demostrar cómo se pasan los parámetros de referencia universales.
template <typename T, typename U>
void g(T&& t, U&& u)
{
std::cout << "t is lvalue ref: "
<< std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
std::cout << "t is rvalue ref: "
<< std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
std::cout << "u is lvalue ref: "
<< std::is_lvalue_reference<decltype(u)>::value << std::endl; // 1
std::cout << "u is rvalue ref: "
<< std::is_rvalue_reference<decltype(u)>::value << std::endl; // 0
}
template <typename T, typename U>
void f(T&& t, U&& u)
{
std::cout << "t is lvalue ref: "
<< std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
std::cout << "t is rvalue ref: "
<< std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
std::cout << "u is lvalue ref: "
<< std::is_lvalue_reference<decltype(u)>::value << std::endl; // 0
std::cout << "u is rvalue ref: "
<< std::is_rvalue_reference<decltype(u)>::value << std::endl; // 1
g(t, u);
}
int main()
{
std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
return 0;
}
El programa muestra que tanto t
y u
pasaron de f
a g
referencias de valor g
, a pesar de que u
es una referencia de valor en f
. Entonces, en el primer caso, el parámetro de forward
simplemente no tiene la oportunidad de ser una referencia de valor.
La identity
se usa para cambiar el tipo de parámetro de referencia universal a una referencia rvalue (como lo menciona Redl, es más preciso usar std::remove_reference
). Sin embargo, este cambio hace que la deducción de tipo de plantilla ya no sea posible, por lo que el parámetro de tipo para forward
es obligatorio, como resultado escribiremos forward<T>(t)
.
Pero el segundo caso en su pregunta tampoco es correcto, como también lo mencionó Redl, el enfoque correcto es una sobrecarga cuyo parámetro es una referencia de valor.
La implementación más directa que puedo encontrar es la siguiente.
template <typename T>
T&& forward(typename identity<T>::type& param)
{
return static_cast<T&&>(param);
}
Funciona para referencias universales, por ejemplo.
template <typename T, typename U>
void f(T&& t, U&& u)
{
::forward<T>(t);
::forward<U>(u);
}
std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
// deduction in f:
// T = unique_ptr&, decltype(t) = unique_ptr&
// U = unique_ptr, decltype(u) = unique_ptr&& (but treated as an lvalue reference)
// specialization of forward:
// forward<T> = forward<unique_ptr&>, param type = unique_ptr&
// return type = unique_ptr&
// forward<U> = forward<unique_ptr>, param type = unique_ptr&
// return type = unique_ptr&&
Estoy leyendo Descripción general del nuevo C ++ (C ++ 11/14) (solo en PDF) , en la diapositiva 288 se incluye una implementación de std::forward
:
template<typename T> // For lvalues (T is T&),
T&& std::forward(T&& param) // take/return lvalue refs.
{ // For rvalues (T is T),
return static_cast<T&&>(param); // take/return rvalue refs.
}
Y luego da otra implementación en texto:
La implementación habitual de std :: forward es:
template<typename T>
struct identity {
typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{ return static_cast<identity<T>::type&&>(param); }
¿Cuál es la diferencia? ¿Por qué esta última es la implementación habitual?
El problema con el primero es que puede escribir std::forward(x)
, que no hace lo que quiere, ya que siempre produce referencias de valores de l.
El argumento en el segundo caso es un contexto no deducido, que impide la deducción automática del argumento de la plantilla. Esto te obliga a escribir std::forward<T>(x)
, que es lo correcto.
Además, el tipo de argumento para la segunda sobrecarga debe ser typename identity<T>::type&
porque la entrada al uso idiomático de std::forward
siempre es un valor l.
Edición: el estándar en realidad exige una firma equivalente a esta (que, por cierto, es exactamente lo que tiene libc ++):
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;
La implementación en libc ++ usa std::remove_reference
y dos sobrecargas. Aquí está la fuente (después de eliminar algunas macros):
template <class T>
inline T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
inline T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
pero tenga en cuenta que en C ++ 14, std::forward
es constexpr
.