Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::vector::push_back() doesn't compile on MSVC for an object with deleted move constructor

I have a class with a deleted move constructor and when I try to call std::vector::push_back() in MSVC (v.15.8.7 Visual C++ 2017) I get an error saying that I am trying to access the deleted move constructor. If however I define the move constructor, the code compiles, but the move constructor is never called. Both versions compile and run as expected on gcc (v. 5.4).

Here's a simplified example:

#include <iostream>
#include <vector>

struct A
{
public:
    A() { std::cout << "ctor-dflt" << std::endl; }
    A(const A&) { std::cout << "ctor-copy" << std::endl; }
    A& operator=(const A&) { std::cout << "asgn-copy" << std::endl; return *this; }
    A(A&&) = delete;
    A& operator=(A&& other) = delete;
    ~A() { std::cout << "dtor" << std::endl; }
};


int main()
{
    std::vector<A> v{};
    A a;
    v.push_back(a);
}

which when compiled on Visual Studio gives the following error:

error C2280: 'A::A(A &&)': attempting to reference a deleted function  

If however I define the move constructor instead of deleting it

 A(A&&) { std::cout << "ctor-move" << std::endl; }

everything compiles and runs, with following output:

ctor-dflt
ctor-copy
dtor
dtor

as expected. No calls to the move constructor. (Live code: https://rextester.com/XWWA51341)

Moreover, both versions work perfectly well on gcc. (Live code: https://rextester.com/FMQERO10656)

So my question is, why doesn't a call to std::vector::push_back() on a non-movable object compile in MSVC, even though the move constructor is apparently never called?

like image 330
Brent Avatar asked Oct 23 '18 09:10

Brent


People also ask

What does the std::vector Push_back () method do?

vector::push_back() push_back() function is used to push elements into a vector from the back. The new value is inserted into the vector at the end, after the current last element and the container size is increased by 1.

Should I use Emplace_back or Push_back?

You should definitely use emplace_back when you need its particular set of skills — for example, emplace_back is your only option when dealing with a deque<mutex> or other non-movable type — but push_back is the appropriate default. One reason is that emplace_back is more work for the compiler.

What does Push_back mean in C++?

push_back is one of the modifiers (used to modify a vector by removal or addition of elements) that exists in STL. C++ push_back() is a pre-defined function that is used to insert data or elements at the end of the vector or it pushes the element in the vector from the back.

Does vector Push_back make a copy?

Yes, std::vector<T>::push_back() creates a copy of the argument and stores it in the vector.


1 Answers

It is undefined behavior, so both gcc and MSVC are correct.

I recently tweeted about a similar case using std::vector::emplace_back with a type that has a deleted move constructor and just like this one it is undefined behavior. So all the compilers are correct here, undefined behavior does not require a diagnostic although implementations are free to do so.

We can see the reasoning by starting out with [container.requirements.general] Table 88 which tells us that push_back requires T be CopyInsertable:

Requires: T shall be CopyInsertable into x

and we can see that CopyInsertable requires MoveInsertable [container.requirements#general]p15:

T is CopyInsertable into X means that, in addition to T being MoveInsertable into X...

In this case A is not MoveInsertable.

We can see this is undefined behavior by looling at [res.on.required]p1:

Violation of the preconditions specified in a function's Requires: paragraph results in undefined behavior unless the function's Throws: paragraph specifies throwing an exception when the precondition is violated.

[res.on.required] comes under Library-wide requirements.

In this case we don't have a throws paragraph so we have undefined behavior, which does not require a diagnostic as we can see from its definition:

behavior for which this International Standard imposes no requirements....

Note, this is very different from being ill-formed which requires a diagnostic, I explain all the detail in my answer here.

like image 158
Shafik Yaghmour Avatar answered Sep 22 '22 07:09

Shafik Yaghmour