0

As I understand rvalue being passed as an argument into function becomes lvalue, std::forward returns rvalue if argument was passed as rvalue and lvalue if it was passed as lvalue. Here is my class:

#include <string>
#include <iostream>

struct MyClass
{
    MyClass()
    {
        std::cout << "default";
    }

    MyClass(const MyClass& copy)
    {
        std::cout << "copy";
    }

    MyClass& operator= (const MyClass& right)
    {
        std::cout << "=";
        return *this;
    }

    MyClass& operator= (const MyClass&& right)
    {
        std::cout << "mov =";
        return *this;
    }

    MyClass(MyClass&& mov)
    {
        std::cout << "mov constructor";
    }
};

void foo(MyClass s)
{
    MyClass z = MyClass(std::forward<MyClass>(s));
}

void main()
{
    auto a = MyClass();
    foo(MyClass()); //z is created by move_constructor
    foo(a); //z is created by move_constructor, but I think it must be created using copy constructor
}

My question is: why z variable is created using move_constructor in both cases. I thought it must be moved in first case foo(MyClass()) and copied in 2nd case foo(a). In second case I pass lvalue as argument s, and std::forward must return lvalue, that is then is passed as lvalue reference into MyClass constructor. Where am I wrong?

Demaunt
  • 1,183
  • 2
  • 16
  • 26

2 Answers2

1

I think you are sufficiently confused. The role of forward is only important when universal references come into play, and universal reference is something like T&& t but only when T is a template parameter.

For example, in void foo(X&& x); x is not a forwarding reference, it is a normal rvalue reference, and forwarding it makes no sense. Rather, you use std::move if you want to preserve it's rvalueness, otherwise it becomes an l-value:

void foo(X&& x) {
     bar(x); // calls bar with an l-value x, x should be not moved from

     baz(std::move(x)); // calls bar with an r-value x, x is likely moved from after this and probably unusable
}

In other words, above function foo was specifically crafted to take rvalue references as it's argument, and will not accept anything else. You, as a function writer, defined it's contract in such way.

In contrast, in a context like template <class T> void foo(T&& t) t is a forwarding reference. Due to reference collapsing rule, it could be an rvalue or an lvalue reference, depending on the valueness of the expression given to the function foo at the call site. In such case, you use

template<class T>
void foo(T&& t) {
    // bar is called with value matching the one at the call site                  
    bar(std::forward<T>(t));
}
SergeyA
  • 61,605
  • 5
  • 78
  • 137
0

The type of the argument that you've declared is MyClass. Whatever is the expression that initialises the argument is irrelevant in the case of your function - it does not affect the type of the argument.

MyClass is not a reference type. std::forward converts an lvalue expression of non-reference type to an rvalue. The use of std::forward in this context is equivalent to std::move.

Note that the argument itself is copy-constructed in the call foo(a).

eerorika
  • 232,697
  • 12
  • 197
  • 326