I'm having compiler errors when trying to emplace_back
in a vector of non-copyable but movable objects with a subtle inheritance twist, that should to my knowledge not change the problem.
Is this legal C++ and this a Visual Studio 2015 bug, or am I making an obvious mistake ?
#include <vector>
class Base
{
public:
Base() {}
Base(Base&) = delete;
Base& operator= (Base&) = delete;
};
class Test : public Base
{
public:
Test(int i) : m_i(i) {}
Test(Test&&) = default;
Test& operator= (Test&&) = default;
protected:
int m_i;
};
int main(int argc, char *argv[])
{
std::vector<Test> vec;
vec.emplace_back(1);
}
Output :
error C2280: 'Test::Test(Test &)': attempting to reference a deleted function
Without the inheritance, that is with deleted copy constructor in Test and no base class, it compiles correctly.
Somehow, removing the default in the move-constructor make it compile correctly also, but then I have to define the move constructor and I don't want to go there.
Which means this compiles fine :
#include <vector>
class Test
{
public:
Test(int i) : m_i(i) {}
Test(Test&) = delete;
Test& operator= (Test&) = delete;
Test(Test&&) = default;
Test& operator= (Test&&) = default;
protected:
int m_i;
};
int main(int argc, char *argv[])
{
std::vector<Test> vec;
vec.emplace_back(1);
}
Puzzling ?
The compiler is correct in all the cases you described.
When Test
is derived from Base
, its defaulted move constructor is defined as deleted because it tries to move Base
, which cannot be move-constructed. Test
is actually not move-constructible in your first example.
In your second example, with no base class, there's nothing to prevent the defaulted move constructor from being defined, so Test
becomes move-constructible.
When you provide a definition for the move constructor, it's up to you to handle Base
. If you just write
Test(Test&&) { }
this will just default-construct a Base
object, so the move constructor will compile, but it probably won't do what you want.
Base
is not move-constructible because it has a user-declared copy constructor, which prevents the implicit declaration of a move constructor - it has no move constructor at all - and its copy constructor is deleted (it couldn't handle rvalues anyway because it takes a non-const reference).
If you make Base
move-constructible, by adding, for example,
Base(Base&&) = default;
then Test
also becomes move-constructible, and your example will compile.
One last piece of the puzzle: since we have declared a move constructor for Test
, why does the error message reference the deleted copy constructor, even in the first case?
For an explanation of std::vector
's logic for choosing which constructor to use to copy / move elements during reallocation, see this answer. Looking at the logic that std::move_if_noexcept uses to choose which kind of reference to return, it will be an rvalue reference in our case (when T
is not copy-constructible, the condition is always false). So, we'd still expect the compiler to attempt to call the move constructor.
However, one more rule comes into play: a defaulted move constructor that is defined as deleted does not participate in overload resolution.
This is done so that construction from an rvalue can fall back to a copy constructor taking a const
lvalue reference, if available. Note that the rule does not apply when the move constructor is explicitly declared as deleted.