I've noticed move
can be applied on objects that does not have a union with a "non-trivial" (don't know exactly, but eg primitive types are fine) member. For example the following code compiles (C++14, Clang):
#include <vector>
#include <string>
class Foo {
public:
union {
int i;
bool b;
};
Foo() {};
~Foo() {};
// Move constructor to default.
Foo(Foo &&) = default;
// Copy constructor deleted.
Foo(const Foo &) = delete;
};
int main() {
std::vector<Foo> v;
v.push_back(Foo());
}
Notice that the copy constructor is deleted. Since std::vector
's push_back
can accept an rvalue reference it will use that in this case and no copy
will occur. However once a "non-trivial" type is added to the union the copy constructor is forced - so it will not compile:
#include <vector>
#include <string>
class Foo {
public:
union {
int i;
bool b;
std::string s; // <-- Added element causing compile error.
};
Foo() {};
~Foo() {};
// Move constructor to default.
Foo(Foo &&) = default;
// Copy constructor deleted.
Foo(const Foo &) = delete;
};
int main() {
std::vector<Foo> v;
v.push_back(Foo());
}
The relevant part of the compiler error message:
In file included from experiment/miniso.cpp:1:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/vector:61:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/allocator.h:46:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/x86_64-linux-gnu/c++/7.2.0/bits/c++allocator.h:33:
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/ext/new_allocator.h:136:23: error: call to deleted constructor of 'Foo'
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/alloc_traits.h:475:8: note: in instantiation of function template
specialization '__gnu_cxx::new_allocator<Foo>::construct<Foo, Foo>' requested here
{ __a.construct(__p, std::forward<_Args>(__args)...); }
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/vector.tcc:100:21: note: in instantiation of function template
specialization 'std::allocator_traits<std::allocator<Foo> >::construct<Foo, Foo>' requested here
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/bits/stl_vector.h:954:9: note: in instantiation of function template
specialization 'std::vector<Foo, std::allocator<Foo> >::emplace_back<Foo>' requested here
{ emplace_back(std::move(__x)); }
^
experiment/miniso.cpp:24:5: note: in instantiation of member function 'std::vector<Foo, std::allocator<Foo> >::push_back' requested here
v.push_back(Foo());
^
experiment/miniso.cpp:19:3: note: 'Foo' has been explicitly marked deleted here
Foo(const Foo &) = delete;
^
I know about a few rules regarding what can be moved and what cannot, however this does not seem trivial to handle. Why is this happening and how could this be solved so it does not call the copy constructor?
Target compiler is Clang C++14.
It's trying to call your copy constructor because your move constructor is deleted. Oh sure, I know you wrote = default
. But since the union contains a type with a non-trivial move/copy constructor, the union's copy/move constructor will be implicitly deleted if it is not user provided.
And = default
does not make it "user provided".
To put it another way, the compiler can't give a union
a copy/move constructor if any of its members require copying/moving code other than memcpy
(aka: not being trivially copyable). And therefore, the type that contains the union cannot have compiler-generated copying/moving code either. In these cases, it is you who must decide how to copy/move the object.
And such a copy/move constructor needs to know which type it is.