Raw pointer lookup for sets of unique_ptrs

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.


Another possibility, close to the accepted answer, but a little different and simplified.

We can exploit the fact that standard comparator std::less<> (with no template arguments) is transparent. Then, we can supply our own comparison functions in the global namespace:

// These two are enough to be able to call objects.find(raw_ptr)
bool operator<(const unique_ptr<Object>& lhs, const Object* rhs) {
  return std::less<const Object*>()(lhs.get(), rhs);
}
bool operator<(const Object* lhs, const unique_ptr<Object>& rhs) {
  return std::less<const Object*>()(lhs, rhs.get());
}

class MyClass
{
  // ...

private:
  std::set<std::unique_ptr<Object>, std::less<>> objects;  // Note std::less<> here
};

You can try to use boost::multi_index_container with additional indexing by Object*. Something like this:

typedef std::unique_ptr<Object> Ptr;
typedef multi_index_container<
  Ptr,
  indexed_by<
    hashed_unique<Ptr>,
    ordered_unique<const_mem_fun<Ptr,Object*,&Ptr::get> >
  >
> Objects;

Fore more information see Boost Multi-index Containers documentation

Or may be you can use std::shared_ptr everywhere, or use raw pointers in set instead?

Why you need to lookup by raw pinter? If you store it anywhere and check that object with this pointer is valid then better to use std::shared_ptr for storing in container and std::weak_ptr for other objects. In this case before usage you don't need lookup by raw pointer at all.