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?
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.
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.
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.
Yes, std::vector<T>::push_back() creates a copy of the argument and stores it in the vector.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With