1401. Provide support for unique_ptr<T> == nullptr

Section: 23.10 [memory] Status: C++11 Submitter: BSI Opened: 2010-08-25 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [memory].

View all issues with C++11 status.

Discussion:

Addresses GB-99

One reason that the unique_ptr constructor taking a nullptr_t argument is not explicit is to allow conversion of nullptr to unique_ptr in contexts like equality comparison. Unfortunately operator== for unique_ptr is a little more clever than that, deducing template parameters for both arguments. This means that nullptr does not get deduced as unique_ptr type, and there are no other comparison functions to match.

[ Resolution proposed by ballot comment: ]

Add the following signatures to 23.10 [memory] p.1, <memory> header synopsis:

template<typename T, typename D>
bool operator==(const unique_ptr<T, D> & lhs, nullptr_t);
template<typename T, typename D>
bool operator==(nullptr_t, const unique_ptr<T, D> & rhs);
template<typename T, typename D>
bool operator!=(const unique_ptr<T, D> & lhs, nullptr_t);
template<typename T, typename D>
bool operator!=(nullptr_t, const unique_ptr<T, D> & rhs);

[ 2010-11-02 Daniel comments and provides a proposed resolution: ]

The same problem applies to shared_ptr as well: In both cases there are no conversions considered because the comparison functions are templates. I agree with the direction of the proposed resolution, but I believe it would be very surprising and inconsistent, if given a smart pointer object p, the expression p == nullptr would be provided, but not p < nullptr and the other relational operators. According to 8.5.9 [expr.rel] they are defined if null pointer values meet other pointer values, even though the result is unspecified for all except some trivial ones. But null pointer values are nothing special here: The Library already defines the relational operators for both unique_ptr and shared_ptr and the outcome of comparing non-null pointer values will be equally unspecified. If the idea of supporting nullptr_t arguments for relational operators is not what the committee prefers, I suggest at least to consider to remove the existing relational operators for both unique_ptr and shared_ptr for consistency. But that would not be my preferred resolution of this issue.

The number of overloads triple the current number, but I think it is much clearer to provide them explicitly instead of adding wording that attempts to say that "sufficient overloads" are provided. The following proposal makes the declarations explicit.

Additionally, the proposal adds the missing declarations for some shared_ptr comparison functions for consistency.

[ 2010-11-03 Daniel adds: ]

Issue 1297 is remotely related. The following proposed resolution splits this bullet into sub-bullets A and B. Sub-bullet A would also solve 1297, but sub-bullet B would not.

A further remark in regard to the proposed semantics of the ordering of nullptr against other pointer(-like) values: One might think that the following definition might be superior because of simplicity:

template<class T>
bool operator<(const shared_ptr<T>& a, nullptr_t);
template<class T>
bool operator>(nullptr_t, const shared_ptr<T>& a);

Returns: false.

The underlying idea behind this approach is the assumption that nullptr corresponds to the least ordinal pointer value. But this assertion does not hold for all supported architectures, therefore this approach was not followed because it would lead to the inconsistency, that the following assertion could fire:

shared_ptr<int> p(new int);
shared_ptr<int> null;
bool v1 = p < nullptr;
bool v2 = p < null;
assert(v1 == v2);

[2011-03-06: Daniel comments]

The current issue state is not acceptable, because the Batavia meeting did not give advice whether choice A or B of bullet 3 should be applied. Option B will now be removed and if this resolution is accepted, issue 1297 should be declared as resolved by 1401. This update also resyncs the wording with N3242.

Proposed resolution:

Wording changes are against N3242.

  1. Change 23.10.2 [memory.syn] p. 1, header <memory> synopsis as indicated. noexcept specifications are only added, where the guarantee exists, that the function shall no throw an exception (as replacement of "Throws: Nothing". Note that the noexcept additions to the shared_ptr comparisons are editorial, because they are already part of the accepted paper n3195:
    namespace std {
      […]
      // [unique.ptr] Class unique_ptr:
      template <class T> class default_delete;
      template <class T> class default_delete<T[]>;
      template <class T, class D = default_delete<T>> class unique_ptr;
      template <class T, class D> class unique_ptr<T[], D>;
    
      template <class T1, class D1, class T2, class D2>
      bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator!=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator<(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator<=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator>(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator>=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
    
      template <class T, class D>
      bool operator==(const unique_ptr<T, D>& x, nullptr_t) noexcept;
      template <class T, class D>
      bool operator==(nullptr_t, const unique_ptr<T, D>& x) noexcept;
      template <class T, class D>
      bool operator!=(const unique_ptr<T, D>& x, nullptr_t) noexcept;
      template <class T, class D>
      bool operator!=(nullptr_t, const unique_ptr<T, D>& x) noexcept;
      template <class T, class D>
      bool operator<(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator<(nullptr_t, const unique_ptr<T, D>& x);
      template <class T, class D>
      bool operator<=(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator<=(nullptr_t, const unique_ptr<T, D>& x);
      template <class T, class D>
      bool operator>(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator>(nullptr_t, const unique_ptr<T, D>& x);
      template <class T, class D>
      bool operator>=(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator>=(nullptr_t, const unique_ptr<T, D>& x);
      
      // [util.smartptr.weakptr], Class bad_weak_ptr:
      class bad_weak_ptr;
    
      // [util.smartptr.shared], Class template shared_ptr:
      template<class T> class shared_ptr;
    
      // [util.smartptr.shared.cmp], shared_ptr comparisons:
      template<class T, class U>
      bool operator==(shared_ptr<T> const& a, shared_ptr<U> const& b)  noexcept;
      template<class T, class U>
      bool operator!=(shared_ptr<T> const& a, shared_ptr<U> const& b)  noexcept;
      template<class T, class U>
      bool operator<(shared_ptr<T> const& a, shared_ptr<U> const& b)  noexcept;
      template<class T, class U>
      bool operator>(shared_ptr<T> const& a, shared_ptr<U> const& b)  noexcept;
      template<class T, class U>
      bool operator<=(shared_ptr<T> const& a, shared_ptr<U> const& b)  noexcept;
      template<class T, class U>
      bool operator>=(shared_ptr<T> const& a, shared_ptr<U> const& b)  noexcept;
    
      template<class T>
      bool operator==(shared_ptr<T> const& a, nullptr_t) noexcept;
      template<class T>
      bool operator==(nullptr_t, shared_ptr<T> const& a) noexcept;
      template<class T>
      bool operator!=(shared_ptr<T> const& a, nullptr_t) noexcept;
      template<class T>
      bool operator!=(nullptr_t, shared_ptr<T> const& a) noexcept;
      template<class T>
      bool operator<(shared_ptr<T> const& a, nullptr_t) noexcept;
      template<class T>
      bool operator<(nullptr_t, shared_ptr<T> const& a) noexcept;
      template>class T>
      bool operator>(shared_ptr<T> const& a, nullptr_t) noexcept;
      template>class T>
      bool operator>(nullptr_t, shared_ptr<T> const& a) noexcept;
      template<class T>
      bool operator<=(shared_ptr<T> const& a, nullptr_t) noexcept;
      template<class T>
      bool operator<=(nullptr_t, shared_ptr<T> const& a) noexcept;
      template>class T>
      bool operator>=(shared_ptr<T> const& a, nullptr_t) noexcept;
      template>class T>
      bool operator>=(nullptr_t, shared_ptr<T> const& a) noexcept;
    
      […]
    }
    
  2. Change the synopsis just after 23.11.1 [unique.ptr] p. 6 as indicated:
    namespace std {
      […]
      
      template <class T1, class D1, class T2, class D2>
      bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator!=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator<(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator<=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator>(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
      template <class T1, class D1, class T2, class D2>
      bool operator>=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
    
      template <class T, class D>
      bool operator==(const unique_ptr<T, D>& x, nullptr_t) noexcept;
      template <class T, class D>
      bool operator==(nullptr_t, const unique_ptr<T, D>& x) noexcept;
      template <class T, class D>
      bool operator!=(const unique_ptr<T, D>& x, nullptr_t) noexcept;
      template <class T, class D>
      bool operator!=(nullptr_t, const unique_ptr<T, D>& x) noexcept;
      template <class T, class D>
      bool operator<(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator<(nullptr_t, const unique_ptr<T, D>& x);
      template <class T, class D>
      bool operator<=(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator<=(nullptr_t, const unique_ptr<T, D>& x);
      template <class T, class D>
      bool operator>(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator>(nullptr_t, const unique_ptr<T, D>& x);
      template <class T, class D>
      bool operator>=(const unique_ptr<T, D>& x, nullptr_t);
      template <class T, class D>
      bool operator>=(nullptr_t, const unique_ptr<T, D>& x);
    
    }
    
  3. This bullet does now only suggest the first approach:

    Change 23.11.1.5 [unique.ptr.special] p. 4-7 as indicated and add a series of prototype descriptions:

    template <class T1, class D1, class T2, class D2>
      bool operator<(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
    

    ? Requires: Let CT be common_type<unique_ptr<T1, D1>::pointer, unique_ptr<T2, D2>::pointer>::type. Then the specialization less<CT> shall be a function object type ([function.objects]) that induces a strict weak ordering ([alg.sorting]) on the pointer values.

    4 Returns: less<CT>()(x.get(), y.get())x.get() < y.get().

    ? Remarks: If unique_ptr<T1, D1>::pointer is not implicitly convertible to CT or unique_ptr<T2, D2>::pointer is not implicitly convertible to CT, the program is ill-formed.

    template <class T1, class D1, class T2, class D2>
      bool operator<=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
    

    5 Returns: !(y < x)x.get() <= y.get().

    template <class T1, class D1, class T2, class D2>
      bool operator>(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
    

    6 Returns: (y < x)x.get() > y.get().

    template <class T1, class D1, class T2, class D2>
      bool operator>=(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);
    

    7 Returns: !(x < y)x.get() >= y.get().

    template <class T, class D>
    bool operator==(const unique_ptr<T, D>& x, nullptr_t) noexcept;
    template <class T, class D>
    bool operator==(nullptr_t, const unique_ptr<T, D>& x) noexcept;
    

    ? Returns: !x.

    template <class T, class D>
    bool operator!=(const unique_ptr<T, D>& x, nullptr_t) noexcept;
    template <class T, class D>
    bool operator!=(nullptr_t, const unique_ptr<T, D>& x) noexcept;
    

    ? Returns: (bool) x.

    template <class T, class D>
    bool operator<(const unique_ptr<T, D>& x, nullptr_t);
    template <class T, class D>
    bool operator>(nullptr_t, const unique_ptr<T, D>& x);
    

    ? Requires: The specialization less<unique_ptr<T, D>::pointer> shall be a function object type ([function.objects]) that induces a strict weak ordering ([alg.sorting]) on the pointer values.

    ? Returns: less<unique_ptr<T, D>::pointer>()(x.get(), nullptr).

    template <class T, class D>
    bool operator<(nullptr_t, const unique_ptr<T, D>& x);
    template <class T, class D>
    bool operator>(const unique_ptr<T, D>& x, nullptr_t);
    

    ? Requires: The specialization less<unique_ptr<T, D>::pointer> shall be a function object type ([function.objects]) that induces a strict weak ordering ([alg.sorting]) on the pointer values.

    ? Returns: less<unique_ptr<T, D>::pointer>()(nullptr, x.get()).

    template <class T, class D>
    bool operator<=(const unique_ptr<T, D>& x, nullptr_t);
    template <class T, class D>
    bool operator>=(nullptr_t, const unique_ptr<T, D>& x);
    

    ? Returns: !(nullptr < x).

    template <class T, class D>
    bool operator<=(nullptr_t, const unique_ptr<T, D>& x);
    template <class T, class D>
    bool operator>=(const unique_ptr<T, D>& x, nullptr_t);
    

    ? Returns: !(x < nullptr).

  4. Change 23.11.3 [util.smartptr.shared] p. 1, class template shared_ptr synopsis as indicated. For consistency reasons the remaining normal relation operators are added as well:

    namespace std {
    
      […]
    
      // [util.smartptr.shared.cmp], shared_ptr comparisons:
      template<class T, class U>
      bool operator==(const shared_ptr<T>& a, const shared_ptr<U>& b) noexcept;
      template<class T, class U>
      bool operator!=(const shared_ptr<T>& a, const shared_ptr<U>& b) noexcept;
      template<class T, class U>
      bool operator<(const shared_ptr<T>& a, const shared_ptr<U>& b) noexcept;
      template<class T, class U>
      bool operator>(const shared_ptr<T>& a, const shared_ptr<U>& b) noexcept;
      template<class T, class U>
      bool operator<=(const shared_ptr<T>& a, const shared_ptr<U>& b) noexcept;
      template<class T, class U>
      bool operator>=(const shared_ptr<T>& a, const shared_ptr<U>& b) noexcept;
    
      template<class T>
      bool operator==(const shared_ptr<T>& a, nullptr_t) noexcept;
      template<class T>
      bool operator==(nullptr_t, const shared_ptr<T>& a) noexcept;
      template<class T>
      bool operator!=(const shared_ptr<T>& a, nullptr_t) noexcept;
      template<class T>
      bool operator!=(nullptr_t, const shared_ptr<T>& a) noexcept;
      template<class T>
      bool operator<(const shared_ptr<T>& a, nullptr_t) noexcept;
      template<class T>
      bool operator<(nullptr_t, const shared_ptr<T>& a) noexcept;
      template>class T>
      bool operator>(const shared_ptr<T>& a, nullptr_t) noexcept;
      template>class T>
      bool operator>(nullptr_t, const shared_ptr<T>& a) noexcept;
      template<class T>
      bool operator<=(const shared_ptr<T>& a, nullptr_t) noexcept;
      template<class T>
      bool operator<=(nullptr_t, const shared_ptr<T>& a) noexcept;
      template>class T>
      bool operator>=(const shared_ptr<T>& a, nullptr_t) noexcept;
      template>class T>
      bool operator>=(nullptr_t, const shared_ptr<T>& a) noexcept;
    
      […]
    }
    
  5. Add the following series of prototype specifications at the very end of 23.11.3.7 [util.smartptr.shared.cmp]. For mixed comparison the general "generation" rule of 20.4.2.3 [operators] p. 10 does not apply, therefore all of them are defined. Below wording takes advantage of the simplified definition of the composite pointer type if one partner is a null pointer constant:
    template<class T>
    bool operator==(const shared_ptr<T>& a, nullptr_t) noexcept;
    template<class T>
    bool operator==(nullptr_t, const shared_ptr<T>& a) noexcept;
    

    ? Returns: !a.

    template<class T>
    bool operator!=(const shared_ptr<T>& a, nullptr_t) noexcept;
    template<class T>
    bool operator!=(nullptr_t, const shared_ptr<T>& a) noexcept;
    

    ? Returns: (bool) a.

    template<class T>
    bool operator<(const shared_ptr<T>& a, nullptr_t) noexcept;
    template<class T>
    bool operator>(nullptr_t, const shared_ptr<T>& a) noexcept;
    

    ? Returns: less<T*>()(a.get(), nullptr).

    template<class T>
    bool operator<(nullptr_t, const shared_ptr<T>& a) noexcept;
    template<class T>
    bool operator>(const shared_ptr<T>& a, nullptr_t) noexcept;
    

    ? Returns: less<T*>()(nullptr, a.get()).

    template<class T>
    bool operator<=(const shared_ptr<T>& a, nullptr_t) noexcept;
    template<class T>
    bool operator>=(nullptr_t, const shared_ptr<T>& a) noexcept;
    

    ? Returns: !(nullptr < a).

    template<class T>
    bool operator<=(nullptr_t, const shared_ptr<T>& a) noexcept;
    template<class T>
    bool operator>=(const shared_ptr<T>& a, nullptr_t) noexcept;
    

    ? Returns: !(a < nullptr).