Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force a compile time error if std::move will result in an unintended copy?

Tags:

In his GoingNative 2013 talk, Scott Meyers pointed out that std::move is no guarantee that the generated code will actually perform a move.

Example:

void foo(std::string x, const std::string y) {   std::string x2 = std::move(x); // OK, will be moved   std::string y2 = std::move(y); // compiles, but will be copied } 

Here, the move constructor cannot be applied but because of overload resolution, the normal copy constructor will be used instead. This fallback option may be crucial for backward compatibility with C++98 code, but in the example above it is most likely not what the programmer intended.

Is there a way to enforce that a move constructor will be called?

For example, assume that you want to move a huge matrix. If your application really depend on the Matrix to be moved, it would be great to immediately get a compile error if a move is not possible. (Otherwise, you the performance problem may slip easily through unit tests and you will only find out after some profiling.)

Lets call this guaranteed move strict_move. I would like to be able to write code like this:

void bar(Matrix x, const Matrix y) {   Matrix x2 = strict_move(x); // OK   Matrix y2 = strict_move(y); // compile error } 

Is it possible?

Edit:

Thanks for the great answers! There were some legitimate requests to clarify my question:

  • Should strict_move fail if the input is const?
  • Should strict_move fail if the result will not lead to an actual move operation (even though the copy might be as fast as a move, e.g., const complex<double>)?
  • Both?

My original idea was very vague: I considered Scott Meyers examples quite alarming, so I wondered if it is possible to have the compiler prevent such unintended copies.

Scott Meyers mentioned in his talk that a general compiler warning is not an option as it would result in a huge number a false positives. Instead I want to communicate to the compiler something like "I'm 100% sure that this must always resulting in a move operation and a copy is too expensive for this specific type".

Thus, I would have offhandedly said that strict_move should fail in both cases. Meanwhile I'm not sure what would be best. Another aspects that I didn't consider is noexcept.

From my side, the exact semantics of strict_move are open. Everything that helps to prevent some dumb mistakes at compile time without having serious drawbacks is fine.

like image 420
Philipp Claßen Avatar asked Sep 05 '13 22:09

Philipp Claßen


People also ask

What happens when you to std :: move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Does std :: move do anything?

std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.

Can I use object after std :: move?

In general, it is perfectly safe to assign to an object that has been an argument to std::move . (A particular class might cause an assertion, but that would be a very odd design.)

Where is std :: move defined?

In C++11, std::move is a standard library function that casts (using static_cast) its argument into an r-value reference, so that move semantics can be invoked. Thus, we can use std::move to cast an l-value into a type that will prefer being moved over being copied. std::move is defined in the utility header.


1 Answers

I advise against writing a general strict_move that is detecting const. I think that is not really what you're looking for. Do you want this to flag a const complex<double>, or a const pair<int, int>? These types will copy as fast they move. Flagging them would just be an irritant.

If you want to do this, I recommend instead checking to see if the type is noexcept MoveConstructible. This will work perfectly for std::string. If the copy constructor of string is accidentally called, it is not noexcept, and therefore will be flagged. But if the copy constructor of pair<int, int> is accidentally called, do you really care?

Here is a sketch of what this would look like:

#include <utility> #include <type_traits>  template <class T> typename std::remove_reference<T>::type&& noexcept_move(T&& t) {     typedef typename std::remove_reference<T>::type Tr;     static_assert(std::is_nothrow_move_constructible<Tr>::value,                   "noexcept_move requires T to be noexcept move constructible");     static_assert(std::is_nothrow_move_assignable<Tr>::value,                   "noexcept_move requires T to be noexcept move assignable");     return std::move(t); } 

I decided to check against is_nothrow_move_assignable as well, as you don't know whether the client is constructing or assigning the lhs.

I opted for internal static_assert instead of an external enable_if because I don't expect noexcept_move to be overloaded, and the static_assert will yield a clearer error message when triggered.

like image 73
Howard Hinnant Avatar answered Oct 20 '22 09:10

Howard Hinnant