unique_ptr<T> == nullptr
Section: 20.2 [memory] Status: C++11 Submitter: BSI Opened: 2010-08-25 Last modified: 2016-01-28
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 20.2 [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
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 someshared_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 objectp
, the expressionp == nullptr
would be provided, but notp < nullptr
and the other relational operators. According to 7.6.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 bothunique_ptr
andshared_ptr
and the outcome of comparing non-null pointer values will be equally unspecified. If the idea of supportingnullptr_t
arguments for relational operators is not what the committee prefers, I suggest at least to consider to remove the existing relational operators for bothunique_ptr
andshared_ptr
for consistency. But that would not be my preferred resolution of this issue.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 ofnullptr
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.
<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; […] }
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); }
Change 20.3.1.6 [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
becommon_type<unique_ptr<T1, D1>::pointer, unique_ptr<T2, D2>::pointer>::type
. Then the specializationless<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 toCT
orunique_ptr<T2, D2>::pointer
is not implicitly convertible toCT
, 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)
.
Change 20.3.2.2 [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; […] }
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)
.