The code below prints l-value
:
#include <iostream>
namespace
{
void foo(const int&)
{
std::cout << "const l-value" << std::endl;
}
void foo(int&)
{
std::cout << "l-value" << std::endl;
}
void foo(int&&)
{
std::cout << "r-value" << std::endl;
}
}
int main()
{
int&& x = 1;
foo(x);
return 0;
}
The type of variable x
is int&&
, but the value category of expression x
is l-value
.
That is why we need std::forward
:
template <typename T>
void deduce(T&& x)
{
//at this point expression x is l-value,
//because it is a named variable.
foo(std::forward<T>(x));
}
int main()
{
//we pass r-value
deduce(1);
return 0;
}
The code below demonstrates the difference between std::move
and std::forward
:
namespace
{
void foo(const int&)
{
std::cout << "const l-value" << std::endl;
}
void foo(int&)
{
std::cout << "l-value" << std::endl;
}
void foo(int&&)
{
std::cout << "r-value" << std::endl;
}
template <typename T>
void func(T&& x)
{
//foo(std::forward<T>(x));
foo(std::move(x));
}
}
int main()
{
int a = 1;
func(a);
const int b = 3;
func(b);
//we pass r-value
func(3);
return 0;
}
With std::forward
it prints
l-value
const l-value
r-value
but with std::move
it prints
r-value
const l-value
r-value
std::move
does remove_reference_t<_Ty>
so reference collapsing rules do not work:
_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
while std::forward
does a similar cast, but without remove_reference_t<_Ty>
::
_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {
return static_cast<_Ty&&>(_Arg);
}
_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
std::forward has a single use case: to cast a templated function parameter (inside the function) to the value category (lvalue or rvalue) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues, and lvalues to be passed on as lvalues, a scheme called “perfect forwarding.”