I wanted to try to add a move assignment operator to my Mesh
class below, knowing that a vector of Meshes appear as a field member in my Model
class:
#include <vector>
struct Mesh {
std::vector<int> vertexes;
Mesh() {
}
Mesh& operator=(Mesh&& m) {
vertexes = std::move(m.vertexes);
return *this;
}
};
struct Model {
std::vector<Mesh> meshes;
Model() {
meshes.resize(10);
}
};
int main() {
Model model;
return 0;
}
This code doesn't work though. Below is the error I'm getting (clang was more explicit than gcc):
In file included from script.cpp:1:
In file included from /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/vector:63:
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:90:7: error: static_assert failed due to requirement 'is_constructib
le<Mesh, Mesh &&>::value' "result type must be constructible from input type"
static_assert(is_constructible<_ValueType, _Tp>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:182:4: note: in instantiation of function template specialization 's
td::__check_constructible<Mesh, Mesh &&>' requested here
= _GLIBCXX_USE_ASSIGN_FOR_INIT(_ValueType2, _From);
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:101:13: note: expanded from macro '_GLIBCXX_USE_ASSIGN_FOR_INIT'
&& std::__check_constructible<T, U>()
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:372:19: note: in instantiation of function template specialization '
std::uninitialized_copy<std::move_iterator<Mesh *>, Mesh *>' requested here
return std::uninitialized_copy(__first, __last, __result);
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:396:19: note: in instantiation of function template specialization '
std::__uninitialized_copy_a<std::move_iterator<Mesh *>, Mesh *, Mesh>' requested here
return std::__uninitialized_copy_a
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/vector.tcc:674:14: note: in instantiation of function template specialization 'std::__un
initialized_move_if_noexcept_a<Mesh *, Mesh *, std::allocator<Mesh>>' requested here
std::__uninitialized_move_if_noexcept_a(
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_vector.h:1011:4: note: in instantiation of member function 'std::vector<Mesh>::_M_de
fault_append' requested here
_M_default_append(__new_size - size());
^
script.cpp:20:12: note: in instantiation of member function 'std::vector<Mesh>::resize' requested here
meshes.resize(10);
In file included from script.cpp:1:
In file included from /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/vector:62:
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_construct.h:119:25: error: call to implicitly-deleted copy constructor of 'Mesh'
::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:120:11: note: in instantiation of function template specialization '
std::_Construct<Mesh, Mesh>' requested here
std::_Construct(std::__addressof(*__cur), *__first);
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:137:16: note: in instantiation of function template specialization '
std::__do_uninit_copy<std::move_iterator<Mesh *>, Mesh *>' requested here
{ return std::__do_uninit_copy(__first, __last, __result); }
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:185:2: note: in instantiation of function template specialization 's
td::__uninitialized_copy<false>::__uninit_copy<std::move_iterator<Mesh *>, Mesh *>' requested here
__uninit_copy(__first, __last, __result);
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:372:19: note: in instantiation of function template specialization '
std::uninitialized_copy<std::move_iterator<Mesh *>, Mesh *>' requested here
return std::uninitialized_copy(__first, __last, __result);
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_uninitialized.h:396:19: note: in instantiation of function template specialization '
std::__uninitialized_copy_a<std::move_iterator<Mesh *>, Mesh *, Mesh>' requested here
return std::__uninitialized_copy_a
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/vector.tcc:674:14: note: in instantiation of function template specialization 'std::__un
initialized_move_if_noexcept_a<Mesh *, Mesh *, std::allocator<Mesh>>' requested here
std::__uninitialized_move_if_noexcept_a(
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.2.0/../../../../include/c /12.2.0/bits/stl_vector.h:1011:4: note: in instantiation of member function 'std::vector<Mesh>::_M_de
fault_append' requested here
_M_default_append(__new_size - size());
^
script.cpp:20:12: note: in instantiation of member function 'std::vector<Mesh>::resize' requested here
meshes.resize(10);
^
script.cpp:9:9: note: copy constructor is implicitly deleted because 'Mesh' has a user-declared move assignment operator
Mesh& operator=(Mesh&& m) {
^
I do realize that:
- std::vector::resize() requires that the value_type is default constructible (hence the empty constructor Mesh::Mesh()).
- The move assignment operator (like the move constructor) deletes the copy constructor and copy assignment operator.
My questions are:
Why is
std::vector::resize()
trying to call the deleted copy constructor to allocate space for new items, when an empty constructor is defined?Is it failing because one of
std::vector::resize()
's requirements isn't met?If so how do I meet it without removing the move assignment? (if I do my code works, same with resize()).
CodePudding user response:
std::vector::resize()
requires T
to be MoveInsertible and DefaultInsertable.
Per MoveInsertible:
If
A
isstd::allocator<T>
, then this will call placement-new, as by::new((void*)p) T(rv)
(until C 20)std::construct_at(p, rv)
(since C 20). This effectively requiresT
to be move constructible.If
std::allocator<T>
or a similar allocator is used, a class does not have to implement a move constructor to satisfy this type requirement: a copy constructor that takes aconst T&
argument can bind rvalue expressions. If a MoveInsertable class implements a move constructor, it may also implement move semantics to take advantage of the fact that the value ofrv
after construction is unspecified.
And per Move constructors:
Implicitly-declared move constructor
If no user-defined move constructors are provided for a class type (
struct
,class
, orunion
), and all of the following is true:
- there are no user-declared copy constructors;
- there are no user-declared copy assignment operators;
- there are no user-declared move assignment operators;
- there is no user-declared destructor.
then the compiler will declare a move constructor as a non-explicit
inline public
member of its class with the signatureT::T(T&&)
.
Your Mesh
class does not have any user-declared copy/move constructors, but it does have a user-declared move assignment operator. As such, Mesh
does not have a compiler-generated move constructor, so it is not move constructible, and thus does not satisfy the requirements of MoveInsertible, hence the compiler error.
To fix this without removing your move assignment operator, as you requested, you will need to add a user-declared copy constructor and/or move constructor to Mesh
, eg:
struct Mesh {
std::vector<int> vertexes;
Mesh() {
}
Mesh(const Mesh& m) : vertexes(m.vertexes) {
}
// and/or:
Mesh(Mesh&& m) : vertexes(std::move(m.vertexes)) {
}
// in which case, you may as well consider adding this, too:
Mesh& operator=(const Mesh& m) {
if (this != &m) {
vertexes = m.vertexes;
}
return *this;
}
Mesh& operator=(Mesh&& m) {
vertexes = std::move(m.vertexes);
return *this;
}
};
However, std::vector
is fully copyable and movable by itself, so if you can forget your unnecessary requirement and get rid of the move assignment operator altogether, then all of the compiler-generated constructors and assignment operators will suffice for this example:
struct Mesh {
std::vector<int> vertexes;
};
CodePudding user response:
Your class Mesh declares an explicit default constructor, which by default, deletes the move constructor.
Either do not declare a constructor, or declare all these 5 functions:
- default constructor
- copy constructor
- move constructor
- copy operator
- move operator
In most cases, but not all, defaults should work fine.
Example:
#include <vector>
// Quick explanation of the rules of 0, 3 and 5
struct Mesh_rule_of_0 {
std::vector<int> vertices;
// rule of zero. No explicit constructors, nor operator=() defined.
// The compiler will generate implicit defaults for you.
};
struct Model_0 {
std::vector<Mesh_rule_of_0> meshes;
Model_0() { meshes.resize(10); }
};
struct Mesh_rule_of_3 {
std::vector<int> vertices;
// if one of these 3 is defined, then all three must be defined
// The compiler will _try_ to generate implicit move operations for you.
Mesh_rule_of_3() = default;
Mesh_rule_of_3(const Mesh_rule_of_3&) = default;
Mesh_rule_of_3& operator=(const Mesh_rule_of_3&) = default;
};
struct Model_3 {
std::vector<Mesh_rule_of_3> meshes;
Model_3() { meshes.resize(10); }
};
struct Mesh_rule_of_5 {
std::vector<int> vertices;
// rule of 5, if a move operator is defined, all 5 must be
// defined.
Mesh_rule_of_5() = default;
Mesh_rule_of_5(const Mesh_rule_of_5&) = default;
Mesh_rule_of_5(Mesh_rule_of_5&&) = default;
Mesh_rule_of_5& operator=(const Mesh_rule_of_5&) = default;
Mesh_rule_of_5& operator=(Mesh_rule_of_5&&) = default;
};
struct Model_5 {
std::vector<Mesh_rule_of_5> meshes;
Model_5() { meshes.resize(10); }
};
struct Mesh_fail {
std::vector<int> vertices;
// this will fail, because it doesn't satisfy the requirements
// of the rule of 0, nor of the rule of 3 nor of
// the rule of 5
Mesh_fail() {}
Mesh_fail& operator=(Mesh_fail&&) = default;
};
// uncomment for failure
// struct Model_fail {
// std::vector<Mesh_fail> meshes;
// Model_fail() { meshes.resize(10); }
// };
int main() {
Model_0 m0;
Model_3 m3;
Model_5 m5;
// uncomment for failure
// Model_fail mf;
return 0;
}