4

I have a std::set<std::unique_ptr<T>> and I'd like to move it to a std::vector<std::unique_ptr<T>>

#include <set>
#include <vector>
#include <memory>

class C {};

int main()
{
  std::set<std::unique_ptr<const C>> s;
  std::vector<std::unique_ptr<const C>> v;
  std::move(s.begin(), s.end(), std::back_inserter(v));
}

This gives the following error on VS2017:

error C2280: 'std::unique_ptr>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function

Can't we make move iterators to non-const variables from a std::set? What could be a viable solution to this problem?

Sagar V
  • 12,158
  • 7
  • 41
  • 68
tunc
  • 519
  • 6
  • 18

1 Answers1

10

In order to extract move-only elements from a set, the only possibility is to use the extract method, which was added in C++17:

while (!s.empty())
    v.emplace_back(std::move(s.extract(s.begin()).value()));

If you cannot use C++17, it is permissible to modify an element of a set (e.g. using mutable) only if you ensure that it retains the same position in the imposed ordering - that is, as long as it has the same result under your comparator when compared to all other members of the set. You can do this by providing a comparator that orders empty unique pointers before non-empty (note that the standard does not guarantee this) and erasing the modified element immediately after modifying it:

template<class T> struct MutableWrapper { mutable T value; };
template<class T> struct MutableWrapperCompare {
  bool operator()(MutableWrapper<T> const& lhs, MutableWrapper<T> const& rhs) {
    return lhs.value && rhs.value ? lhs.value < rhs.value : rhs.value;
  }
};

int main()
{
  std::set<MutableWrapper<std::unique_ptr<const C>>, MutableWrapperCompare<std::unique_ptr<const C>>> s;
  std::vector<std::unique_ptr<const C>> v;
  while (!s.empty())
  {
    v.emplace_back(std::move(s.begin()->value));
    s.erase(s.begin());
  }
}

This is however rather ugly and dangerous; you would be better off using boost::container::set from Boost.Container, which has the C++17 extract method (since 1.62.0; it was undocumented, but this is just an oversight, note the corresponding extract methods are documented for map and multimap).

metalfox
  • 6,301
  • 1
  • 21
  • 43
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • So is there anyway that I can implement this method for my source base until the compiler I use implements it ? – tunc Jul 11 '17 at 10:12
  • 1
    @tunc sort of, but it's ugly - see above. If at all possible I would recommend using Boost (boost::container::set, v 1.62.0 and above). – ecatmur Jul 11 '17 at 10:35
  • how about the `merge` method ? – v.oddou Apr 04 '19 at 06:42
  • @v.oddou: `merge` is also C++17 – jwd Feb 12 '20 at 00:06
  • There's another approach [described here](https://stackoverflow.com/questions/39810367/how-to-get-a-move-only-type-out-of-a-stl-container/39810384#39810384), which is a little less dangerous, but still not ideal. – jwd Feb 12 '20 at 00:07