Raw pointer lookup for sets of unique_ptrs

c++, c++11, c++14, unique-ptr

Solution

In C++14, `std::set<Key>::find` is a `template` function if `Compare::is_transparent` exists. The type you pass in does not need to be `Key`, just equivalent under your comparator.

So write a comparator:

template<class T>
struct pointer_comp {
  typedef std::true_type is_transparent;
  // helper does some magic in order to reduce the number of
  // pairs of types we need to know how to compare: it turns
  // everything into a pointer, and then uses `std::less<T*>`
  // to do the comparison:
  struct helper {
    T* ptr;
    helper():ptr(nullptr) {}
    helper(helper const&) = default;
    helper(T* p):ptr(p) {}
    template<class U, class...Ts>
    helper( std::shared_ptr<U,Ts...> const& sp ):ptr(sp.get()) {}
    template<class U, class...Ts>
    helper( std::unique_ptr<U, Ts...> const& up ):ptr(up.get()) {}
    // && optional: enforces rvalue use only
    bool operator<( helper o ) const {
      return std::less<T*>()( ptr, o.ptr );
    }
  };
  // without helper, we would need 2^n different overloads, where
  // n is the number of types we want to support (so, 8 with
  // raw pointers, unique pointers, and shared pointers).  That
  // seems silly:
  // && helps enforce rvalue use only
  bool operator()( helper const&& lhs, helper const&& rhs ) const {
    return lhs < rhs;
  }
};

then use it:

typedef std::set< std::unique_ptr<Foo>, pointer_comp<Foo> > owning_foo_set;

now, `owning_foo_set::find` will accept `unique_ptr<Foo>` or `Foo*` or `shared_ptr<Foo>` (or any derived class of `Foo`) and find the correct element.

Outside of C++14, you are forced to use the `map` to `unique_ptr` approach, or something equivalent, as the signature of `find` is overly restrictive. Or write your own `set` equivalent.

Problem

I often find myself wanting to write code like this: ``` class MyClass { public: void addObject(std::unique_ptr<Object>&& newObject); void removeObject(const Object* target); private: std::set<std::unique_ptr<Object>> objects; }; ``` However, much of the std::set interface is kind of useless with std::unique_ptrs since the lookup functions require std::unique_ptr parameters (which I obviously don't have because they're owned by the set itself). I can think of two main solutions to this. Create a temporary unique_ptr for lookup. For example, the above removeObject() could be implemented like: ``` void MyClass::removeObject(const Object* target) { std::unique_ptr<Object> targetSmartPtr(target); objects.erase(targetSmartPtr); targetSmartPtr.release(); } ``` Replace the set with a map of raw pointers to unique_ptrs. ``` // ... std::map<const Object*, std::unique_ptr<Object>> objects; }; ``` However, both seem slightly stupid to me. In solution 1, erase() isn't noexcept, so the temporary unique_ptr might delete the object it doesn't really own, and 2 requires double the storage for the container unnecessarily. I know about Boost's pointer containers, but their current features are limited compared to modern C++11 standard library containers. I was recently reading about C++14 and came across "Adding heterogeneous comparison lookup to associative containers". But form my understanding of it, the lookup types must be comparable to the key types, but raw pointers aren't comparable to unique_ptrs. Anyone know of a more elegant solution or an upcoming addition to C++ that solves this problem?

Original source

Related problems