Following pkasting's recent talk on c++20 comparison and equality operators* in Chrome, I took a look at adding operator<=> to a class that had a WeakPtr member, and found out that WeakPtr doesn't define any equality or comparison operators, not even the old operator<. It would be convenient to define operator<=> so that classes with WeakPtr members could use defaulted comparison operators, but the semantics are complicated.**
A WeakPtr can be in 3 states:
1. Null (after using the default constructor, calling reset(), or assigning nullptr). `ref_` is invalid, `ptr_` is null.
get() returns nullptr.
operator*() and operator->() will CHECK.
operator bool() returns false.
MaybeValid() returns false.
WasInvalidated() returns false.
2. Pointing to a valid object. `ref_` is valid, `ptr_` is non-null.
get() returns non-null.
operator*() and operator->() return pointers to the object.
operator bool() returns true.
MaybeValid() returns true.
WasInvalidated() returns false.
3. Pointing to an invalidated object. `ref_` is invalid, `ptr_` is non-null.
get() returns nullptr.
operator*() and operator->() will CHECK.
operator bool() returns false.
MaybeValid() returns true or false, racily.
WasInvalidated() returns true.
It would be trivial to define operator== as `lhs.get() == rhs.get()` and `lhs.get() <=> rhs.get()`, but that means that an invalidated WeakPtr compares equal to an explicit nullptr. And using WeakPtr in a sorted container would be a footgun, because the ordering would change as pointers are invalidated.
It would also be possible to define them as `lhs.ptr_ == rhs.ptr_` and `lhs.ptr_ <=> rhs.ptr_`, ignoring the validity. That way WeakPtr's containing the same pointer would continue to be equal and have the same ordering after the pointers are invalidated. I think this is safe because it only compares the values of the pointers, never dereferences them.
A third option would be to get more complicated, and define invalidated pointers as undefined, which matches the comment on `ptr_`:
// This pointer is only valid when ref_.is_valid() is true. Otherwise, its
// value is undefined (as opposed to nullptr).
I think the semantic of this would be that operator== always returns false if either argument is an invalidated pointer, and operator<=> returns std::partial_ordering::unordered. But this seems potentially very confusing.
I favour comparing `ptr_` members directly, even though this seems to go against the spirit of the comment. Thoughts?
** Also the only reasonable way to implement the operators would need them to be called on the thread the WeakPtr is bound to, but I don't think that's a problem. It's just a logical extension of the existing semantics, that a WeakPtr can only by dereferenced or checked for validity on a specific thread.
Joe