Home > Back-end >  vector resize with move assignment: result type must be constructible from input type
vector resize with move assignment: result type must be constructible from input type

Time:01-12

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 is std::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 requires T 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 a const 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 of rv 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, or union), and all of the following is true:

then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::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;
}
  • Related