Why How to Use 'Std::Move' on a 'Const' Object

Why can we use `std::move` on a `const` object?

struct strange {
mutable size_t count = 0;
strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

here we see a use of std::move on a T const. It returns a T const&&. We have a move constructor for strange that takes exactly this type.

And it is called.

Now, it is true that this strange type is more rare than the bugs your proposal would fix.

But, on the other hand, the existing std::move works better in generic code, where you don't know if the type you are working with is a T or a T const.

Why does calling std::move on a const object call the copy constructor when passed to another object?

The type of the result of calling std::move with a T const argument is T const&&, which cannot bind to a T&& parameter. The next best match is your copy constructor, which is deleted, hence the error.

Explicitly deleteing a function doesn't mean it is not available for overload resolution, but that if it is indeed the most viable candidate selected by overload resolution, then it's a compiler error.

The result makes sense because a move construction is an operation that steals resources from the source object, thus mutating it, so you shouldn't be able to do that to a const object simply by calling std::move.

Is there any way to correctly std::move const data?

There is no way to std::move a const T as it would mutate the given object. (Unless you make the members mutable and make a T(const T&&) constructor to do the move, which I would not recommend).

A better solution in my opinion would be to make all the mutating functions private, such that only the builder can access them, and return a T&&.

why std::move works with constant object

Because std::move makes moving an object possible. It does not actually move anything. That would be the move constructor of s.

But since the result of the move here will still be const it won't call the move constructor. It will call the copy constructor.

Try it. You'll see.

Moving members of class passed as a const reference argument

Moving const objects is generally a simple copy (unless you overload Object(const Object&&)) so your usage of std::move seems useless.

If the members of ResourceInserter are (non-const) references,
your const ResourceInserter& is "misleading", and your move will actually happen.

Why does std::move copy contents for a rvalue or const lvalue function argument?

Your vector is actually copied, not moved. The reason for this is, although declared as an rvalue reference, vec_ denotes an lvalue expression inside the function body. Thus the copy constructor of std::vector is invoked, and not the move constructor. The reason for this is, that vec_ is now a named value, and rvalues cannot have names, so it collapses to an lvalue. The following code will fail to compile because of this reason:

void foo(int&& i)
{
int&& x = i;
}

In order to fix this issue, you have to make vec_ nameless again, by calling std::move(vec_).

Passing the const-qualified object to the 'std::move'

Your code:

std::make_pair<const int, const int>( std::move( MIN_ALLOWED_Y ), std::move( MAX_ALLOWED_Y ) )

Is overly complicated. Not only are the moves pointless as PVS Studio told you, but using make_pair when explicitly specifying the types is pointless. You can simplify to:

std::pair<const int, const int>( MIN_ALLOWED_Y, MAX_ALLOWED_Y )

Which does the same thing without excess ceremony.

what does std::move(const shared_ptr reference) mean?

std::move is a function which converts the argument to an rvalue reference. The function call is an xvalue expression.

When the argument is a reference to const, then the result of the conversion is an rvalue to const. If you initialise from rvalue to const, copy constructor will be used because the rvalue reference parameter to non-const of the move constructor cannot bind to rvalue reference argument to const.

I think there is also an implicit question by OP of how _p(std::move(p)) might differ from _p(p)

_p(std::move(p)) doesn't differ from _p(p) in case of const std::shared_ptr<T>.

In theory, if decltype(_p) was a type that had a constructor T(const T&&), then there would be a difference, since that constructor would be invoked by _p(std::move(p)) but not by _p(p). Such constructor would be quite unconventional, but technically well-formed. std::shared_ptr doesn't have such constructor.

Why move on const objects work?

You're not moving anything.

std::move is really poorly named: it doesn't force a move; it just returns an rvalue. It's up to the compiler to decide which constructor of std::vector<int> to invoke, and that's what determines whether you get a move.

If the container can't be moved because the target's move constructor isn't a match, then the copy constructor will be used instead, through basic overload rules.

#include <iostream>

struct T
{
T() = default;
T(const T&) { std::cout << "copy ctor\n"; }
T(T&&) { std::cout << "move ctor\n"; }
};

int main()
{
T a;
T b = std::move(a); // "move ctor"

const T c;
T d = std::move(c); // "copy ctor" - `const T&&` only matches copy ctor

// (shut up GCC)
(void) b;
(void) d;
}

(live demo)

It's designed this way (const T&& being able to bind to const T&) at least in part because moving is intended to be best-effort, exactly so that you don't have to fight with compiler errors in cases like this.



Related Topics



Leave a reply



Submit