apmccartney
apmccartney

Reputation: 743

std::set member variable copied instead of moved when class instance moved

I've been working with a pair of classes. The former stores meta data and the latter acts as a container and supports various sorts of indexing of based on the meta data. Stripped down versions are posted below.

The latter class uses an std::set to manage its collection of objects of the former class for reasons related to referential stability (pointers to composed meta data objects must remain valid as elements are added and removed).

For reasons I don't understand, the set member of the indexing class is calling to its (deleted) copy constructor even when move semantics ought to be called instead. I have compiled on Apple LLVM 7.0.0 (using libc++) and GCC 4.9 (libstdc++) and recieved similar errors.

Is there some reason the move constructor cannot be called in this case?

#include <ctime>
#include <functional>
#include <set>
#include <string>
#include <memory>
#include <vector>

// used to store meta data about some class T
template<typename T>
struct Foo {
  std::unique_ptr<T> data; // T being some abstract class
  std::string label;
  std::time_t stamp;
  const Foo* parentPtr;

  Foo( std::unique_ptr<T>&& data,
        const std::string& label,
        const std::time_t stamp,
        const Foo* parent ) : data( std::move(data) ),
                         label( label ),
                         stamp( stamp ),
                         parentPtr( parent ){}
};

// ordering of Foo objects should be in terms of the label. Necessary for std::set.
namespace std {
template<typename T>
struct less< Foo<T> > {
  constexpr bool
  operator()( const Foo<T>& lhs,
              const Foo<T>& rhs ) const {
    return lhs.label < rhs.label;
  }
};
}

// collection of Foos
template<typename T>
class Bar{
public:
  Bar() = default;
  Bar( std::set< Foo<T> >&& foos ) : foos( std::move(foos) ){}
  ~Bar() = default;

  // irrelevant helpers omitted

  std::set< Foo<T> > foos;
};

// causes compilation to fail
auto returnTest0 = [](){
    std::set< Foo< std::vector<int> > > S = {};
    return Bar< std::vector<int> >(std::move(S));
    // This is an r-value. Why isn't RVO or move semantics occuring? 
};

// also causes compilation to fail
auto returnTest1 = [](Bar< std::vector<int> >&& b){
    return std::move(b);
};

The compiler errors from gcc 4.9 are reproduced below

        In file included from /usr/include/x86_64-linux-gnu/c++/4.9/bits/c++allocator.h:33:0,
                     from /usr/include/c++/4.9/bits/allocator.h:46,
                     from /usr/include/c++/4.9/string:41,
                     from /usr/include/c++/4.9/stdexcept:39,
                     from /usr/include/c++/4.9/array:38,
                     from /usr/include/c++/4.9/tuple:39,
                     from /usr/include/c++/4.9/functional:55,
                     from 2:
    /usr/include/c++/4.9/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = Foo<std::vector<int> >; _Args = {const Foo<std::vector<int, std::allocator<int> > >&}; _Tp = std::_Rb_tree_node<Foo<std::vector<int> > >]':

    /usr/include/c++/4.9/bits/alloc_traits.h:253:4:   required from 'static std::_Require<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type> std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = Foo<std::vector<int> >; _Args = {const Foo<std::vector<int, std::allocator<int> > >&}; _Alloc = std::allocator<std::_Rb_tree_node<Foo<std::vector<int> > > >; std::_Require<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type> = void]'

    /usr/include/c++/4.9/bits/alloc_traits.h:399:57:   required from 'static 
decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = Foo<std::vector<int> >; _Args = {const Foo<std::vector<int, std::allocator<int> > >&}; _Alloc = std::allocator<std::_Rb_tree_node<Foo<std::vector<int> > > >; decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = <type error>]'

    /usr/include/c++/4.9/bits/stl_tree.h:423:42:   required from 'std::_Rb_tree_node<_Val>* std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_create_node(_Args&& ...) [with _Args = {const Foo<std::vector<int, std::allocator<int> > >&}; _Key = Foo<std::vector<int> >; _Val = Foo<std::vector<int> >; _KeyOfValue = std::_Identity<Foo<std::vector<int> > >; _Compare = std::less<Foo<std::vector<int> > >; _Alloc = std::allocator<Foo<std::vector<int> > >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type = std::_Rb_tree_node<Foo<std::vector<int> > >*]'

    /usr/include/c++/4.9/bits/stl_tree.h:445:53:   required from 'std::_Rb_tree_node<_Val>* std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_clone_node(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Const_Link_type) [with _Key = Foo<std::vector<int> >; _Val = Foo<std::vector<int> >; _KeyOfValue = std::_Identity<Foo<std::vector<int> > >; _Compare = std::less<Foo<std::vector<int> > >; _Alloc = std::allocator<Foo<std::vector<int> > >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type = std::_Rb_tree_node<Foo<std::vector<int> > >*; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Const_Link_type = const std::_Rb_tree_node<Foo<std::vector<int> > >*]'

    /usr/include/c++/4.9/bits/stl_tree.h:1207:43:   required from 'std::_Rb_tree_node<_Val>* std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_copy(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Const_Link_type, std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type) [with _Key = Foo<std::vector<int> >; _Val = Foo<std::vector<int> >; _KeyOfValue = std::_Identity<Foo<std::vector<int> > >; _Compare = std::less<Foo<std::vector<int> > >; _Alloc = std::allocator<Foo<std::vector<int> > >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type = std::_Rb_tree_node<Foo<std::vector<int> > >*; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Const_Link_type = const std::_Rb_tree_node<Foo<std::vector<int> > >*]'

    /usr/include/c++/4.9/bits/stl_tree.h:676:50:   required from 'std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Rb_tree(const std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>&) [with _Key = Foo<std::vector<int> >; _Val = Foo<std::vector<int> >; _KeyOfValue = std::_Identity<Foo<std::vector<int> > >; _Compare = std::less<Foo<std::vector<int> > >; _Alloc = std::allocator<Foo<std::vector<int> > >]'
    /usr/include/c++/4.9/bits/stl_set.h:197:22:   required from 'std::set<_Key, _Compare, _Alloc>::set(const std::set<_Key, _Compare, _Alloc>&) [with _Key = Foo<std::vector<int> >; _Compare = std::less<Foo<std::vector<int> > >; _Alloc = std::allocator<Foo<std::vector<int> > >]'
    36:7:   required from here
    /usr/include/c++/4.9/ext/new_allocator.h:120:4: error: use of deleted function 'Foo<std::vector<int> >::Foo(const Foo<std::vector<int> >&)'
      { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
        ^
    9:8: note: 'Foo<std::vector<int> >::Foo(const Foo<std::vector<int> >&)' is implicitly deleted because the default definition would be ill-formed:
    9:8: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = std::vector<int>; _Dp = std::default_delete<std::vector<int> >]'
    In file included from /usr/include/c++/4.9/memory:81:0,
                     from 5:
    /usr/include/c++/4.9/bits/unique_ptr.h:356:7: note: declared here
           unique_ptr(const unique_ptr&) = delete;
       ^

Upvotes: 2

Views: 234

Answers (1)

T.C.
T.C.

Reputation: 137425

The user-declared destructor

~Bar() = default;

suppresses the implicit generation of Bar's move constructor and move assignment operator.

Either remove the destructor declaration, or explicitly default the move special members:

Bar(Bar&&) = default;
Bar& operator=(Bar&&) = default;

Upvotes: 8

Related Questions