7

I can't seem to figure this out and have tried the suggestions in:

Move `unique_ptr`s between sets

how to move an std::unique_ptr<> from one STL container to another?

I have two sets containing unique pointers:

std::set<std::unique_ptr<some_type>> s1, s2;

The pointers are of course unique but the values of some_type may or may not be, so after joining the s2 into s1, s1's size may be the same or as large as |s1 + s2|.

It seems like I should be able to do this:

move(s2.begin(), s2.end(), inserter(s1, s1.end()));

But this fails with clang++ 3.8 / g++ 5.4.

What am missing here?

Community
  • 1
  • 1
user318904
  • 2,968
  • 4
  • 28
  • 37
  • Wait, do you want to compare the pointed-to values or the unique_ptr's? Assuming it's the former, you should be using a custom Compare template argument for your sets. Are you? – einpoklum Oct 22 '16 at 10:05

3 Answers3

4

It doesn't work because a std::set only gives const access to its elements. There is no way to move something out of a std::set. See Is it possible to move an item out of a std::set?

There's no good way to do this in C++14, but in C++17 there is a merge method provided for this purpose, which simply rearranges the internal pointers of the data structures without copying or moving any elements:

s1.merge(s2);

A somewhat reasonable workaround in C++14 is to change std::set<std::unique_ptr<T>> to std::map<T*, std::unique_ptr<T>>. Then you can do:

while (!s1.empty()) {
    s2[s1.begin()->first] = std::move(s1.begin()->second);
    s1.erase(s1.begin());
}
Community
  • 1
  • 1
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 2
    Or you can use `shared_ptr` instead of `unique_ptr`. – David Schwartz Oct 21 '16 at 23:39
  • And if you make sure to use `std::make_shared` to make them instead of the `std::shared_ptr` constructor, it shouldn't hurt perf/memory use/memory fragmentation as much (with `std::make_shared`, only a single allocation is required for the reference counting overhead and the object storage, while using `new` and `std::shared_ptr` means two allocations), so it will behave more like `unique_ptr` speed-wise. – ShadowRanger Oct 22 '16 at 00:07
  • Thanks for this good answer (and following comment). I did end up just using a shared_ptr after wrestling with it some more. Bonus mini rant: if no compilers can produce useful error messages (most of the time) for a language it is a language design flaw, not a compiler issue. Anyways its been fun using C++ again.. – user318904 Oct 22 '16 at 19:31
2

Currently you're conflating set membership with ownership, and that's the source of the problem.

Solution: use sets of raw pointers, and move the ownership elsewhere.


Re the envisioned

move(s2.begin(), s2.end(), inserter(s1, s1.end()));

In the standard library move is just a glorified cast, it's not an action routine. The imperative name form is misleading. Think of std::move as as_moveable.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

In C++17 you could use std::set::merge to do what you want as in the example below:

std::set<std::unique_ptr<T>> s1, s2;
...
s1.merge(s2);

Live Demo

A C++11 work around, rather expensive though and a bit of a betrayal to unique_ptr's notion, that would also require that your objects are copyable. Would be to copy the objects of the second std::set in new std::unique_ptrs that you are going to insert in your first set:

std::set<std::unique_ptr<T>> s1, s2;
...
for(auto &&e : s2) {
  s1.insert(std::make_unique<T>(*e));
}
s2.clear();

mind that at the end you would have to clear your second set in order to recover from the hangout caused by the wild parting of std::unique_ptr's notion.

Live Demo

Community
  • 1
  • 1
101010
  • 41,839
  • 11
  • 94
  • 168