3

In the following code I want to move-construct an object that has no move-constructor available:

class SomeClass{

public:
    SomeClass() = default;

    SomeClass(const SomeClass&)  = default;
    SomeClass(      SomeClass&&) = delete;

};

SomeClass& getObject(){

    return some_obj;

};

//...

SomeClass obj = std::move( getObject());

The compiler gives an error: "use of deleted function". This is all good.

On the other hand, if it has a move-constructor but getObject() returns a const object, then the copy constructor will be called instead, even though I'm trying to move it with std::move.

Is it possible to make the compiler give a warning / error that std::move won't have any effect since the object can't be moved?

class SomeClass{

public:
    SomeClass() = default;

    SomeClass(const SomeClass&)  = default;
    SomeClass(      SomeClass&&) = default;

};

const SomeClass& getObject(){

    return some_obj;

};

//...

SomeClass obj = std::move( getObject());
AdyAdy
  • 988
  • 6
  • 19
  • 5
    `std::move` does not have an effect in the "it moves something" sense anyways, it's just a cast. Granted, the name is misleading. – Baum mit Augen Feb 20 '18 at 12:29
  • 2
    @BaummitAugen My point is that the caller clearly expects a move to happen, but it won't happen. It would be nice to see a warning. – AdyAdy Feb 20 '18 at 12:39
  • 1
    You probably could create your own `adyady::move` which is a template cast that also tests the type for move-ability and if not available trips a compiler error. I'm not familiar with template metaprogramming to cobble that together off the cuff. – Eljay Feb 20 '18 at 13:09
  • 1
    If you *always* want to move, you can delete the copy constructor instead. :-) Anyway, as Baum says, `std::move` isn't moving anything; it just says that it is *allowed*, but not required, to move the source object. The result depends on what the target will do, not on the source. – Bo Persson Feb 20 '18 at 13:11

3 Answers3

1

If your concern is just knowing for sure that you're getting the best possible performance, most of the time you should probably just trust the compiler. In fact, you should almost never need to use std::move() at all. In your example above, for example, it's not having an effect. Modern compilers can work out when a move should happen.

If you have classes that should always be moved and never copied, then delete their copy constructors.

But maybe you're writing a template function that is going to have terrible performance if you pass it a class without a move constructor, or you're in some other situation I haven't thought of. In that case, std::is_move_constructible is what you probably want. Try this:

#include <type_traits>

#include <boost/serialization/static_warning.hpp>

template<class T>
T &&move_or_warn(T &t)
{
    BOOST_STATIC_WARNING(std::is_move_constructible<T>::value);
    return std::move(t);
}

Now if you do SomeClass obj = std::move_or_warn( getObject());, you should get a compiler warning if the object can't be moved. (Although I'd probably use the normal std::move and call std::is_move_constructible seperately.)

Unfortunately, C++ doesn't (yet) have a standard way to produce the sort of programmer-specified warning you're looking for, which is why I had to use boost. Take a look here for more discussion about generating warnings.

1

The problem comes from const rvalue. Such arguments match const T& better than T&&. If you really want to ban such arguments, you can add an overloaded move constructor:

SomeClass(const SomeClass&&) = delete;

Note: What you are trying is to ban such arguments, rather to ban the move behavior. Because we are usually not able to "steal" resource from a const object even if it is an rvalue, it is reasonable to call copy constructor instead of move constructor. If this is an XY problem, you should consider if it is really intended to ban such arguments.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
1

Is it possible to make the compiler give a warning / error that std::move won't have any effect since the object can't be moved?

It is not exactly true that std::move won't have any effect. The following code (try it on wandbox):

void foo(const SomeClass&) {
    std::cout << "calling foo(const SomeClass&)" << std::endl;
}

void foo(const SomeClass&&) {
    std::cout << "calling foo(const SomeClass&&)" << std::endl;
}

int main() {
    foo(getObject());
    foo(std::move(getObject()));
}

will output

calling foo(const SomeClass&)
calling foo(const SomeClass&&)

even though your object has a deleted move constructor. The reason is that std::move doesn't by itself "move" anything. It just does a simple cast (C++17 N4659 draft, 23.2.5 Forward/move helpers):

template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;

Returns: static_cast<remove_reference_t<T>&&>(t)

This is why the compiled doesn't give a warning - everything is perfectly legal, the cast you are doing has nothing to do with the deleted move constructor, and overload resolution selects the copy constructor as the best match.

Of course, you can define your own move with different semantics than std::move if you really need such semantics (like the one in matthewscottgordon's answer).

gflegar
  • 1,583
  • 6
  • 22