Martel
Martel

Reputation: 2734

C++ compiling error due to a conflict between the movement constructor and other non-movement one when using std::move

I am doing some "experiments" with move semantics in order to fully understand it. I have reached a point that I don't understand completely. This is because it seems that there is a conflict with a constructor like A::A(B b) and A::A(B &&b) when using std::move. Below is the code that explains this:

struct Item {
    Item() { }

    Item(const Item &other) {
        std::cout << "Item::Item(const Item& )\n";
    }
};

struct Container {
    Container(Item i) {
        std::cout << "Container::Container(Item )\n";
    }

    Container(Item &&i) {
        std::cout << "Container::Container(Item&& )\n";
    }
};

int main() {
    Item i;
    Container c(std::move(i));
    return 0;
}

When trying to compile the above code, the followign error is shown:

error C2668: 'Container::Container' : ambiguous call to overloaded function
1>          c:\users\xe74139\documents\visual studio 2012\projects\project1\project1\maincpp.cpp(21): could be 'Container::Container(Item &&)'
1>          c:\users\xe74139\documents\visual studio 2012\projects\project1\project1\maincpp.cpp(17): or       'Container::Container(Item)'

I know that the constructor Container(Item i) makes no sense at all, but I still don't understand why C++ compiler sees a conflict between both Container constructors.

Why it can't determine that Container c(i) is for calling Container(Item) and Container c(std::move(i)) is for calling Container(Item &&)?

EDIT: Or, in other words, why the constructor Container(Item) is valid for a call like Container c(std::move(i))?

Upvotes: 2

Views: 132

Answers (2)

einpoklum
einpoklum

Reputation: 131656

Why the constructor Container(Item) is valid for a call like Container c(std::move(i))?

Because you can construct an Item when given the result of std::move(i) (which is an Item&&).

And like HowardHinnant said in a comment - it's the same for Item vs Item& - you can construct one when passed the other, so if you have both of these constructors defined you've created amibguity.

Upvotes: 1

kabanus
kabanus

Reputation: 25895

As far as I can tell, it's because the argument first evaluated, then passed. Once it's passed, it's just any r-value, and both your constructors would work. I'm not sure it's possible to have such an overload, and it doesn't make sense from my point of view.

Why not? Well, you are trying to define a container that forces movement of an element. From a design view, you have to consider that someone using that code may not want the data "stolen" (moved). I think it would make more sense in this context to have:

struct Item {
    Item() { }

    Item(const Item &other) {
        std::cout << "Item::Item(const Item& )\n";
    }
    Item(Item &&other) {
        std::cout << "Item::Item(Item&&)\n";
    }
};

This overload makes more sense - you will explicitly state when you want the object moved, and when copied (there's a default move here, but this way makes it more obvious). Now you just need

Container(Item i);

(without the other constructor) and calling it with

Container c(std::move(i));

is what we expect - instead of copying i, you're explicitly stating you want it moved, and i is in charge of handling this.

Upvotes: 2

Related Questions