Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Value can be moved even if passed by value

Tags:

c++

In the following program, how is it possible for the move to complete if the parameter was passed by value?

Both constructor signatures; MyContainer(Myclass&& myclass) and MyContainer(Myclass myclass) work equally well, but Myclass only has a move constructor, the rest are deleted.

#include <iostream>

class Myclass {
public:
    Myclass(int value) : value{value} {std::cout << "Constructed with value " << value << "\n";}
    Myclass(Myclass&& other) : value{other.value}
    {
        other.value = 0;
        std::cout << "Move constructed with value " << value << "\n";
    }
    Myclass(const Myclass& other) = delete;
    Myclass& operator=(const Myclass& other) = delete;
    Myclass& operator=(Myclass&& other) = delete;
    ~Myclass() {std::cout << "Destructed with value " << value << "\n";}
private:
    int value;
};

class MyContainer {
public:
    //MyContainer(Myclass&& myclass) : myclass{std::move(myclass)} { } // Shouldn't this be the constructor definition?
      MyContainer(Myclass   myclass) : myclass{std::move(myclass)} { } // this also works, why?
private:
    Myclass myclass;
};

int main()
{
    Myclass myclass{5};
    MyContainer mycontainer{std::move(myclass)};

    return 0;
}

When the constructor is called I do use std::move, but how does the compiler 'remember' that it was moved if the parameter was Myclass and not Myclass&&?

like image 207
wally Avatar asked Jun 08 '16 14:06

wally


2 Answers

In both cases you are casting your lvalue myclass to an rvalue reference.

The return type of std::move is an rvalue reference, so by definition, the std::move expression is an rvalue - and as such, can bind to the rvalue reference parameter of the move constructor of MyClass.

The std::move instruction can be interpreted as 'I'm done with this variable, it can now be moved'. Note that this is the case irrespective of what the type of the variable being passed to std::move is - it works just as well with normal variables, lvalue reference and rvalue references.

like image 102
Smeeheey Avatar answered Sep 29 '22 06:09

Smeeheey


When you write MyContainer mycontainer{std::move(myclass)};, we have to enumerate all the viable constructors of MyContainer that can be called with an xvalue of type Myclass. MyContainer has a single relevant constructor which takes an argument of type Myclass. To see if that constructor is viable, we effectively perform overload resolution again on constructing a Myclass with an xvalue of type Myclass. That gives us three constructors:

Myclass(int );                     // not viable: no conversion from `Myclass` to `int`
Myclass(Myclass&& );               // viable
Myclass(const Myclass& ) = delete; // viable

The move constructor is viable because you can bind an rvalue reference to an xvalue. The copy constructor is viable because you can bind a const lvalue reference to anything. One of the tiebreakers in overload resolution is picking the least cv-qualified reference. The move constructor is an rvalue reference to Myclass. The copy constructor is an lvalue reference to Myclass const - that is more cv-qualified, hence it is less prefered. Thus, we select the move constructor. This constructor is not deleted, so that call is well-formed.

The important part here is that we're constructing with an xvalue of type Myclass (that's what std::move() does - it's a cast to xvalue). If we were constructing with an lvalue (i.e. simply MyContainer mycontainer(myclass)) then only the copy constructor would be viable, but it's explicitly deleted so the entire expression is ill-formed.

how does the compiler 'remember' that it was moved if the parameter was Myclass and not Myclass&&?

There's no remembering. In both cases, we're trying to initialize the argument in the constructor from an xvalue of type Myclass. In the former case, that involves invoking the move constructor of Myclass. In the latter case, that's a direct reference binding.

like image 32
Barry Avatar answered Sep 29 '22 05:09

Barry