Enabling the Use of weak_ptr as Keys in Unordered
            Associative Containers
        
        Contents
Abstract
            This paper proposes the addition of ownership based hashing and
            equality, to enable the use of weak_ptr as keys in
            unordered associative containers.
        
Motivation
            I recently found myself wanting to store a collection of
            weak_ptr objects.  I neither had a separate key nor
            any requirements on the ordering of the objects.  It seemed that
            using an unordered_set would be a good fit.  It
            quickly became apparent, however, that it’s simply not possible;
            there is no way to generate a stable hash or compare
            weak_ptr objects without support from the Standard
            Library.
        
Also relevant is the discussion in LWG Issue 1406, which labelled this as not a defect, and recommended a paper be brought forward to propose the addition.
Proposal
owner_hash and owner_equal
        
            The obvious, and in my opinion best, option is to follow in the
            footsteps of owner_less, which can be used to allow
            weak_ptr keys in ordered associative containers.
        
            N2637 was the paper through which owner_less was
            introduced into the standard.
        
The following methods and class templates would be added:
- 
                
size_t shared_ptr::owner_hash() const noexcept; - 
                
bool shared_ptr::owner_equal(…) const noexcept; - 
                
size_t weak_ptr::owner_hash() const noexcept; - 
                
bool weak_ptr::owner_equal(…) const noexcept; - 
                
template <class T> struct owner_hash; - 
                
template <class T> struct owner_equal; 
            shared_ptr and weak_ptr would gain two
            new methods each: owner_hash and
            owner_equal.  owner_hash would return a
            ownership based hash and owner_equal would compare
            ownership.
        
            In addition, two new class templates would be provided:
            owner_hash and owner_equal.
        
            This option has the benefit of providing all the tools necessary
            for having a weak_ptr key in an unordered associative
            container.
        
            The addition of the methods and template specializations for
            shared_ptr enables the ability to look up values in an
            unordered associative container from either the
            weak_ptr or the corresponding shared_ptr.
            It also enables storing shared_ptr keys based on the
            ownership equivalence relation, if such a thing is desired.
        
            I don't believe this proposal exposes any more implementation
            details than are already exposed through owner_less.
        
            To satisfy the Cpp17Hash requirements that the probability
            of a collision be low, a conforming implementation could simply
            hash the address of the control block, per
            hash<void *>.
        
Alternatives Considered
            owner_ptr / owner / owner_id
        
        This option would instead add the following methods:
- 
                
const unspecified shared_ptr::owner_ptr() const noexcept; - 
                
const unspecified weak_ptr::owner_ptr() const noexcept; 
            The unspecified return type would need to be something for
            which there is, or could be, a std::hash
            specialization and an equality comparator (for example, a pointer
            to the control block, a guid stored in the control block, etc.).
        
            This option would look to provide the bare minimum required,
            leaving it up to the user to define owner_hash and
            owner_equal in terms of this method.
        
shared_ptr_key / weak_ptr_key
        
            This option would provide wrapper classes,
            shared_ptr_key and weak_ptr_key, which
            would have a std::hash specialization and an equality
            comparator.  They would presumably be friends of
            shared_ptr and weak_ptr respectively.
        
Do Nothing
            Whilst writing this paper, I discovered that it is actually
            possible for end users to do this right now in terms of
            owner_before, it just wouldn't be efficient.
        
            owner_hash would be implemented to return a constant
            value — completely going against the Cpp17Hash
            requirements which state thatthe probability of a collision be
            low.
        
and
            owner_equal(p,q) would be implemented to return
            !p.owner_before(q) && !q.owner_before(p).
        
Wording
Class template owner_hash [util.smartptr.ownerhash]
        Add this entire section.
            The class template owner_hash allows ownership-based
            hashing.
        
namespace std {
  template <class T> struct owner_hash;
  template <class T> struct owner_hash<shared_ptr<T>> {
    size_t operator()(const shared_ptr<T>&) const noexcept;
  };
  template <class T> struct owner_hash<weak_ptr<T>> {
    size_t operator()(const weak_ptr<T>&) const noexcept;
  };
}
        
            operator()(x) shall return x.owner_hash().
        
Class template owner_equal [util.smartptr.ownerequal]
        Add this entire section.
            The class template owner_equal allows
            ownership-based mixed equality comparisons of shared and
            weak pointers.
        
namespace std {
  template <class T = void> struct owner_equal;
  template <class T> struct owner_equal<shared_ptr<T>> {
    bool operator()(const shared_ptr<T>&, const shared_ptr<T>&) const noexcept;
    bool operator()(const shared_ptr<T>&, const weak_ptr<T>&) const noexcept;
    bool operator()(const weak_ptr<T>&, const shared_ptr<T>&) const noexcept;
  };
  template <class T> struct owner_equal<weak_ptr<T>> {
    bool operator()(const weak_ptr<T>&, const weak_ptr<T>&) const noexcept;
    bool operator()(const shared_ptr<T>&, const weak_ptr<T>&) const noexcept;
    bool operator()(const weak_ptr<T>&, const shared_ptr<T>&) const noexcept;
  };
  template <> struct owner_equal<void> {
    template <class T, class U>
      bool operator()(const shared_ptr<T>&, const shared_ptr<U>&) const noexcept;
    template <class T, class U>
      bool operator()(const shared_ptr<T>&, const weak_ptr<U>&) const noexcept;
    template <class T, class U>
      bool operator()(const weak_ptr<T>&, const shared_ptr<U>&) const noexcept;
    template <class T, class U>
      bool operator()(const weak_ptr<T>&, const weak_ptr<U>&) const noexcept;
    using is_transparent = unspecified;
  };
}
        
            operator()(x, y) shall return x.owner_equal(y).
        
Class template shared_ptr [util.smartptr.shared]
        Add to the observers section:
size_t owner_hash() const noexcept; template<class U> bool owner_equal(const shared_ptr<U>& b) const noexcept; template<class U> bool owner_equal(const weak_ptr<U>& b) const noexcept;
Observers [util.smartptr.shared.obs]
Add at the end:
size_t owner_hash() const noexcept;
Returns: An unspecified value such that
            — x.owner_hash() == y.owner_hash() is
            true if
            x.owner_equal() == y.owner_equal()
        
template<class U> bool owner_equal(const shared_ptr<U>& b) const noexcept; template<class U> bool owner_equal(const weak_ptr<U>& b) const noexcept;
            Returns: true if *this and
            b share ownership or are both empty.
        
Class template weak_ptr [util.smartptr.weak]
        Add to the observers section:
size_t owner_hash() const noexcept; template<class U> bool owner_equal(const shared_ptr<U>& b) const noexcept; template<class U> bool owner_equal(const weak_ptr<U>& b) const noexcept;
Observers [util.smartptr.weak.obs]
Add at the end:
size_t owner_hash() const noexcept;
Returns: An unspecified value such that
            — x.owner_hash() == y.owner_hash() is
            true if
            x.owner_equal() == y.owner_equal()
        
template<class U> bool owner_equal(const shared_ptr<U>& b) const noexcept; template<class U> bool owner_equal(const weak_ptr<U>& b) const noexcept;
            Returns: true if *this and
            b share ownership or are both empty.
        
Class template enable_shared_from_this [util.smartptr.enab]
        
            It might make sense to change the example assert from
        
assert(!p.owner_before(q) && !q.owner_before(p)); // p and q share ownership
to
assert(p.owner_equal(q)); // p and q share ownership
References
- LWG Issue 1406 — http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#1406
 - 
                N2637 Revisiting 
std::shared_ptrcomparison — https://wg21.link/n2637 - Cpp17Hash requirements — [tab:cpp17.hash] in https://wg21.link/n4830
 
Acknowledgements
            Thanks to Alexander Jones, Alisdair Meredith and Masud Rahman for
            their help reviewing, suggesting alternatives, and providing
            background on the history of owner_less.