Search code examples
c++c++11lvalue

Why doesn't std::forward preserve the lvalue-ness of this variable?


In the code below (which is run in C++20), when I call the UseForward function, I expect the first overload to be called (which is template <typename T> void UseForward(T& value)) and it does get called. Then in the body of the function I use std::forward which I expect to preserve the lvalue-ness of variable value and call the copy constructor, BUT it calls the move constructor. What am I missing here?

class SomeClass
{
public:
    SomeClass()
    {
        std::cout << "default constructor" << std::endl;
    }

    SomeClass(const SomeClass& other)
    {
        std::cout << "const copy constructor" << std::endl;
    }

    SomeClass(SomeClass&& other) noexcept
    {
        std::cout << "move constructor" << std::endl;
    }
};

template <typename T>
void UseForward(T& value)
{
    std::cout << "UseForward pass by reference!" << std::endl;
    auto sc = SomeClass(std::forward<T>(value));
}

template <typename T>
void UseForward(T&& value)
{
    std::cout << "UseForward pass by rvalue reference!" << std::endl;
    auto sc2 = SomeClass(std::forward<T>(value)); 
}

int main()
{
    auto sc = SomeClass();
    UseForward(sc);
}

Solution

  • Comment out first definition of UseForward and behold, both

    UseForward(sc);
    UseForward(SomeClass());
    

    would say that you had passed value by rvalue reference. That's because && with cv-unqualified type T (T&&) in substitution context is NOT an rvalue reference but so-called forwarding reference, it preserves a value cathegory.

    For desired effect you have to use type traits, by SFINAE or by compile-time alternatives:

    template <typename T>
    void UseForward(T&& value)
    {
        if constexpr (std::is_rvalue_reference_v<decltype(value)>)
            std::cout << "UseForward pass by rvalue reference!" << std::endl;
        else
            std::cout << "UseForward pass by reference!" << std::endl;
        auto sc2 = SomeClass(std::forward<T>(value)); 
    }