3

Let's consider an std::unordered_set of std::unique_ptr<T> as an example. Can I move an element of the set elsewhere ?

#include <unordered_set>
#include <iostream>
#include <memory>
#include <vector>

int main()
{
    std::unordered_set<std::unique_ptr<int>> mySet;

    mySet.insert(std::make_unique<int>(1));
    mySet.insert(std::make_unique<int>(2));
    mySet.insert(std::make_unique<int>(3));

    std::vector<std::unique_ptr<int>> myVector;

    for (auto&& element : mySet)
    {
        std::cout << *element << std::endl;
        //myVector.push_back(element); won't compile as you can only get a const ref to the key
    }
}

I have a very practical example of code where I would like to do this but am reduced to use a std::shared_ptr. Do you know of another (better ?) alternative ?

matovitch
  • 1,264
  • 11
  • 26
  • 1
    Note that this problem does not occur if `mySet` were a `std::vector` instead; it is peculiar to [node-based containers](http://en.cppreference.com/w/cpp/container/node_handle). Also note that if you succeed in moving it, it may make other operations on that set undefined behavior since the moved-from object is no longer valid to read from, e.g., for insertion or finding, so caution must be exercised in using the container at all. – metal Oct 01 '16 at 19:17
  • @metal I am probably doing something wrong but I am having the same compilation error with vectors only, See: http://coliru.stacked-crooked.com/a/51af3ac220c44619. edit: My bad, works with a `std::move`. Thanks ! The new code: http://coliru.stacked-crooked.com/a/95cb8a827d587723 – matovitch Oct 01 '16 at 19:24
  • There's a useful technique described [here](https://stackoverflow.com/questions/60220220/efficiently-erase-a-unique-ptr-from-an-unordered-set/60220391#60220391) which is efficient, if ugly. That code is for erasing, but can be modified to move/extract instead. – jwd Mar 28 '20 at 07:57

1 Answers1

9

In C++03, C++11, and C++14, not directly. You'd have to change the type to be something like:

template <class T>
struct handle {
    mutable std::unique_ptr<T> owning_ptr;
    T* observing_ptr; // enforce that observing_ptr == owning_ptr.get() on construction

    // define operator<, hash, etc. in terms of the observing ptr
};

With this, you can write:

std::unordered_set<handle<int>> mySet;
// initialize as appropriate

for (auto& elem : mySet) {
    myVector.push_back(std::move(elem.owning_ptr));        
}
mySet.clear();

This will still be well-defined behavior because we're not messing with any of the container internals - the observing pointer will still be valid through the end of the clear(), just now myVector owns it instead.


In C++17, we can do this directly and more simply with the help of extract():

for (auto it = mySet.begin(); it != mySet.end();  
{
    std::cout << **it << std::endl;
    myVector.push_back(std::move(
        mySet.extract(it++).value()));
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • You could make the container contain a mutable tagged union-like of the smart pointer and a dumb pointer that agree on ordering, and an operation that swaps from smart to dumb while returning the smart. The extract the smart, erase the dumb, and bob is your uncle. – Yakk - Adam Nevraumont Oct 01 '16 at 19:03
  • @Yakk One step ahead of you :) Except made it not a union, but yeah it could be. – Barry Oct 01 '16 at 19:04
  • 1
    Should that not be `handle` rather than `handle>`? – sjrowlinson Oct 01 '16 at 19:08
  • @Yakk and @Barry Thanks, I think I will go for this handy `handle` as a union ! ;) – matovitch Oct 01 '16 at 19:08
  • A pair or struct is union-like. ;) You could also use a unique ptr with a special deleter that understands move-to default-delete as "transform to dumb pointer". – Yakk - Adam Nevraumont Oct 01 '16 at 19:15