c++c++17movesyntactic-sugar

How do I move an l-value to a function implicitly, with no explicit call to std::move()?


I have a movable, but not copyable, class Service, another class Collection and a function Collection::Attach() which should take an object of type Service as an argument.

The context makes it clear that collection object takes ownership of all attached services, and that once attached the service must not be used directly any more. Which naturally translates to moving a service object to the collection, and suggests that Collection::Attach() should be declared like

void Attach(Service);

But then, I anticipate the following use cases marked (1) and (2):

#include <utility>

struct Service
{
    Service() = default;
    Service(Service&&) = default;       // Move ok.
    Service(const Service&) = delete;   // No copy.
};

struct Collection
{
    // How do I declare this method so that
    // use cases (1) and (2) shown below both work?
    void Attach(Service);
};

void foo(Collection* collection)
{
    // This works fine:
    collection->Attach(Service{});            // (1)
}

void bar(Collection* collection)
{
    Service      s;

    // This works fine but 'std::move()' seems redundant:
    collection->Attach(std::move(s));

    // I want the following line to work
    // asif 'std::move()' was there:
    collection->Attach(s);                   // (2)
}

and I would very much like to lift the burden of typing std::move() in use case (2) off client programmers. Again, the context and names make it clear that the argument is moved.

The question: Can I do this without overloading Attach()?

void Attach(Service&&);
void Attach(Service&);

because that looks ugly to me.


Solution

  • Ƴou could define a non-const-taking copy constructor and make that actually move-construct:

    struct Service
    {
      Service(Service& other){
        // move-construct from other.
      }
    };
    

    But note that that is a very bad idea.

    Otherwise you will have to overload Attach to also take a Service& and move out of the passed-in reference. Which is also a bad idea, but less bad than the previous one.

    You already know what do to and implemented it correctly. No, std::move does not look pretty and C++ would probably be better off if move semantics where the default. But they're not. If you want that, take a look at Rust.

    The context makes it clear that collection object takes ownership of all attached services, and that once attached the service must not be used directly any more.

    I highly doubt that "the context" makes that clear – if I didn't move my object away (via std::move), I will assume that I can still access it.