Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

STL associative containers: erasing and getting back the (noncopyable) element

I am using STL associative containers (std::set and std::map) with keys which hold a std::unique_ptr<> instance. The key definition is equivalent to following:

struct Key
{
    std::unique_ptr<Object> object;
    bool operator== (const Key& rhs) const { return object->equal (*rhs.object); }
    bool operator<  (const Key& rhs) const { return object->less (*rhs.object); }
}

It is known that STL associative containers (esp. since C++11) do not have a way to obtain a non-const reference to the key to move from. And my keys are noncopyable, so c++: Remove element from container and get it back does not work.

Is there a non-UB way to overcome this problem?

My current solution is following:

template <typename T>
using map_pair_type = std::pair<typename T::key_type, typename T::mapped_type>;

template <typename T>
typename T::value_type take_set (T& container, typename T::iterator iterator)
{
    typename T::value_type result = std::move (const_cast<typename T::value_type&> (*iterator));
    container.erase (iterator);
    return result;
}

template <typename T>
map_pair_type<T> take_map (T& container, typename T::iterator iterator)
{
    map_pair_type<T> result {
        std::move (const_cast<typename T::key_type&> (iterator->first)),
        std::move (iterator->second)
    };
    container.erase (iterator);
    return result;
}
like image 727
intelfx Avatar asked Dec 27 '14 20:12

intelfx


1 Answers

This is one of these:

Really sorry. We tried to make this work and couldn't get it through committee.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3586.pdf

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3645.pdf

Your solution is as good as it gets as far as I know. Your map solution does exhibit undefined behavior. And it will go seriously bad if the second move throws an exception. Other than that, I suspect it will work. And I suspect I'll get down voted for saying that.

The reason for the UB is that the key is defined as const (as opposed to just being referenced by a const reference). Casting away const in that situation (and having a move constructor modify the object) is UB.

Had N3586 been accepted, you could just:

move_only_type mot = move(*s.remove(s.begin()));

or:

move_only_key mok = move(m.remove(m.begin())->first);

N3586/N3645 made a good showing in committee. It was discussed and made it through the working group stage, only to be shot down in full committee. The concern is that the std::lib would have to commit UB in order to implement it. It has not been resubmitted.

Update

One can now do this in C++17, but the member function is called extract instead of remove.

like image 133
Howard Hinnant Avatar answered Dec 29 '22 15:12

Howard Hinnant