Value category is a property of an expression in C++ or std::forward explained

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);
}

1 Response to Value category is a property of an expression in C++ or std::forward explained

  1. dmitriano says:

    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.”

Leave a Reply

Your email address will not be published. Required fields are marked *