During the attempt to provide a resolution for issue 834 it became evident that this was not the only place in the standard, where a named collection of type requirements would be useful that could be used as a placeholder of a pointer-like type. While there are generally more aspects than a nullable characteristics of a pointer, we need only the common subset of operations that applies to function pointers, object pointers, pointers-to-member, and void pointers. Further-on we would like to support user-defined types that behave similarily to these types (e.g. smart pointers and std::exception_ptr). These requirements exclude the possibility to add a dereference operation to the set of supported operations (cv void* and allocator::[const_]void_pointer do not support it).
The intent of this paper is to keep the proposal as simple as possible but still attempting to produce wording that would be compatible with non-object template parameter T of unique_ptr<T, D> as an extension of the previous wording. Specifically, this paper does not suggest to extend the default_delete class template for C++0x to cope with function types, but such an extension is recommended as part of a future TR2.
The fundamental approach used in this proposal is not completely new: It bases on the ideas that had been worked out during the short concept era by Alisdair Meredith in N2853.
At this point it is probably worth to mention that the well-known nullable type family boost::optional does not satisfy the here suggested NullablePointer requirements. One formal reason is that it uses boost::none_t instead of std::nullptr_t, which is arguably related to the fact that the latter is a quite new part of the C++ standard. The more relevant reason is, that boost::optional<T> does not satisfy all construction and assignment requirements of a NullablePointer, because they depend on the actual argument type for T (in particular the question whether these operations may throw exceptions). This deviation is nothing to worry about. In fact, the boost documentation specifically emphasizes that boost::optional does not model a pointer. If the standard wishes to extract a more fundamental set of requirements for any form of a nullable type in the future, this is still possible and this is the reason why this proposal suggests the name NullablePointer instead of Nullable for the here proposed requirements set.
Other naming choices as replacement of NullablePointer were considered, e.g. NullableHandle, but these alternatives turned out to be less advantageous, especially since std::nullptr_t is used as part of the specification and all current models of these have names which end with pointer.
This proposal discussed here addresses the library issues 834, 1293, and 1307 and provides a more satisfactory solution for issue 1135; additionally it incorporates the following issues with ready state: 932, 950, 983, 1100.
During the work on this issue it turned out, that [unique.ptr.single]/2 is unclear, whether its list of requirements to the deleter (MoveConstructible, MoveAssignable, Swappable) is purely descriptive and optional or not. If optional, some members, especially the move-constructor and the move-assignment operator of unique_ptr need to add missing requirements. The wording correction is done as part of this proposal as well.
1 - The type exception_ptr can be used to refer to an exception object.
2 - exception_ptr shall
be DefaultConstructible, CopyConstructible, CopyAssignable, and EqualityComparable. exception_ptr's operations shall not throw exceptionssatisfy the NullablePointer requirements ([nullablepointer.requirements]).3 - Two
objectsnon-null values of type exception_ptr are equivalent and compare equal if and only if they refer to the same exception.4 - The default constructor of exception_ptr produces the null value of the type.
The null value is equivalent only to itself.5 -
An object of type exception_ptr can be compared for equality with a null pointer constant and assigned a null pointer constant. The effect shall be as if exception_ptr() had been used in place of the null pointer constant.exception_ptr shall not be implicitly convertible to arithmetic type, to enumeration type or to pointer type.6 - [ Note: An implementation might use a reference-counted smart pointer as exception_ptr. — end note ]
20.2.2 NullablePointer requirements [nullablepointer.requirements]
A NullablePointer is a pointer-like type that supports null values.
A type P meets the NullablePointer requirements, if
A value-initialized object of P produces the null value of the type. The null value shall be equivalent only to itself. A default-initialized object of P may have an indeterminate value. [Note: Operations involving indeterminate values may trigger undefined behavior. — end note]
An object p of this type can be contextually converted to bool ([conv]). The effect shall be as if p != nullptr had been evaluated in place of p.
No operation which is part of the NullablePointer requirements shall exit via an exception.
In Table ??, u denotes an identifier, t denotes a non-const lvalue of type P, a and b denote values of type (possibly const) P, and np denotes a value of type (possibly const) std::nullptr_t.
Table ?? — Additional NullablePointer requirements Expression Return type Operational semantics P u(np);
P u = np;post: u == nullptr P(np) post: P(np) == nullptr t = np P& post: t == nullptr a != b contextually convertible to bool !(a == b) a == np
np == acontextually convertible to bool a == P() a != np
np != acontextually convertible to bool !(a == np)
template <class T, class D = default_delete<T>> class unique_ptr { public: ... // constructors ... template <class U, class E> unique_ptr(unique_ptr<U, E>&& u); template <class U> unique_ptr(auto_ptr<U>& u); template <class U> unique_ptr(auto_ptr<U>&& u); ... };
1 - The default type for the template parameter D is default_delete. A client-supplied template argument D shall be a function
pointer or functorobject type ([function.objects]), lvalue-reference to function, or lvalue-reference to function object type for which, given a value d of type D and apointervalue ptr of typeT*unique_ptr<T, D>::pointer, the expression d(ptr) is valid and has the effect of deallocating the pointer as appropriate for that deleter.D may also be an lvalue-reference to a deleter.2 - If the deleter D maintains state, it is intended that this state stay with the associated pointer as ownership is transferred from unique_ptr to unique_ptr. The deleter state need never be copied, only moved or swapped as pointer ownership is moved around.
That is, the deleter need only be MoveConstructible, MoveAssignable, and Swappable, and need not be CopyConstructible (unless copied into the unique_ptr) nor CopyAssignable.If the deleter is not a reference type, D shall satisfy the Destructible requirements ([destructible]).3 - If the type remove_reference<D>::type::pointer exists, then unique_ptr<T, D>::pointer shall be a synonym for remove_reference<D>::type::pointer. Otherwise unique_ptr<T, D>::pointer shall be a synonym for T*. The type unique_ptr<T, D>::pointer shall
be CopyConstructible (Table 34) and CopyAssignable (Table 36)satisfy the NullablePointer requirements ([nullablepointer.requirements]).[Example: Given an allocator type X ([allocator.requirements]), the types X::pointer, X::const_pointer, X::void_pointer, and X::const_void_pointer may be used as unique_ptr<T, D>::pointer — end example]
constexpr unique_ptr();
1 - Requires: D shall
be default constructiblesatisfy the DefaultConstructible requirements, and that construction shall not throw an exception.D shall not be a reference type or pointer type (diagnostic required).2 - Effects: Constructs a unique_ptr which owns nothing, value-initializing the stored pointer and the stored deleter.
3 - Postconditions: get() ==
0nullptr. get_deleter() returns a reference toa value-initializedthe stored deleter D.4 - Throws: nothing.
Remarks: If this constructor is instantiated with a pointer type or reference type for the template argument D, the program is ill-formed.
unique_ptr(pointer p);
5 - Requires: D shall
be default constructiblesatisfy the DefaultConstructible requirements, and that construction shall not throw an exception.6 - Effects: Constructs a unique_ptr which owns p, initializing the stored pointer with p and value-initializing the stored deleter.
7 - Postconditions: get() == p. get_deleter() returns a reference to
a default constructedthe stored deleter D.8 - Throws: nothing.
? - Remarks: If this constructor is instantiated with a pointer type or reference type for the template argument D, the program is ill-formed.
unique_ptr(pointer p,
implementation-definedsee below d1);
unique_ptr(pointer p,implementation-definedsee below d2);...
12 - Requires: If D is not an lvalue-reference type then
- If d is an lvalue or const rvalue then the first constructor of this pair will be selected. D
must beshall satisfy the CopyConstructible requirements (Table 34), and this unique_ptr will hold a copy of d. The copy constructor of D shall not throw an exception.- Otherwise d is a non-const rvalue and the second constructor of this pair will be selected. D
need only beshall satisfy the MoveConstructible requirements (Table 33), and this unique_ptr will hold a value move constructed from d. The move constructor of D shall not throw an exception....
Effects: Constructs a unique_ptr which owns p, initializing the stored pointer with p and initializing the deleter as described above.
14 - Postconditions: get() == p. get_deleter() returns a reference to the internally stored deleter. If D is a reference type then get_deleter() returns a reference to the lvalue d.
unique_ptr(unique_ptr&& u);
16 - Requires: If the deleter is not a reference type, D shall satisfy the MoveConstructible requirements and the construction of the deleter D from an rvalue D shall not throw an exception.
17 - Effects: Constructs a unique_ptr which owns the pointer which u owns (if any). If the deleter is not a reference type, it is move constructed from u's deleter, otherwise the reference is copy constructed from u's deleter. After the construction, u no longer owns a pointer. [Note: The deleter constructor can be implemented with std::forward<D>. — end note]
18 - Postconditions: get() == value u.get() had before the construction and u.get() == nullptr. get_deleter() returns a reference to the internally stored deleter which was constructed from u.get_deleter(). If D is a reference type then get_deleter() and u.get_deleter() both reference the same lvalue deleter.
19 - Throws: nothing.
template <class U, class E> unique_ptr(unique_ptr<U, E>&& u);
20 - Requires: If
DE is not a reference type, construction of the deleter D from an rvalue of type E shall be well formed and shall not throw an exception. Otherwise E is a reference type and construction of the deleter D from an lvalue of type E shall be well formed and shall not throw an exception.If D is a reference type, then E shall be the same type as D (diagnostic required). unique_ptr<U, E>::pointer shall be implicitly convertible to pointer. [Note: These requirements imply that T and U are complete types. — end note]Remarks: If D is a reference type, then E shall be the same type as D, else this constructor shall not participate in overload resolution. unique_ptr<U, E>::pointer shall be implicitly convertible to pointer, else this constructor shall not participate in overload resolution. U shall not be an array type, else this constructor shall not participate in overload resolution.
21 - Effects: Constructs a unique_ptr which owns the pointer which u owns (if any). If the deleter E is not a reference type,
itthis deleter is move constructed from u's deleter, otherwisethe referencethis deleter is copy constructed from u's deleter. After the construction, u no longer owns a pointer. [Note: The deleter constructor can be implemented with std::forward<DE>. — end note]22 - Postconditions: get() == value u.get() had before the construction, modulo any required offset adjustments resulting from the cast from unique_ptr<U, E>::pointer to pointer and u.get() == nullptr. get_deleter() returns a reference to the internally stored deleter which was constructed from u.get_deleter().
23 - Throws: nothing.
template <class U> unique_ptr(auto_ptr<U>& u); template <class U> unique_ptr(auto_ptr<U>&& u);Effects: Constructs a unique_ptr, initializing the stored pointer with u.release() and value-initializing the stored deleter.Postconditions: get() == the value u.get() had before the construction, modulo any required offset adjustments resulting from the cast from U* to pointer and u.get() == nullptr. get_deleter() returns a reference to the stored deleter D.
Throws: nothing.
Remarks: U* shall be implicitly convertible to pointer and D shall be the same type as default_delete<T>, else these constructors shall not participate in overload resolution.
~unique_ptr();
1 - Requires: The expression get_deleter()(get()) shall be well formed, shall have well-defined behavior, and shall not throw exceptions. [Note: The use of default_delete requires T to be a complete type. — end note]
2 - Effects: If get() ==
0nullptr there are no effects. Otherwise get_deleter()(get()).3 - Throws: nothing.
unique_ptr& operator=(unique_ptr&& u);
1 - Requires: If the deleter D is not a reference type, D shall satisfy the MoveAssignable requirements and a
Assignment of the deleter D from an rvalue D shall not throw an exception.Otherwise the deleter D is a reference type, remove_reference<D>::type shall satisfy the CopyAssignable requirements, and assignment of the deleter D from an lvalue D shall not throw an exception.2 - Effects: reset(u.release()) followed by an
moveassignment fromu's deleter to this deleterstd::forward<D>(u.get_deleter()).3 - Postconditions: This unique_ptr now owns the pointer which u owned, and u no longer owns it, u.get() == nullptr.
[Note: If D is a reference type, then the referenced lvalue deleters are move assigned. — end note]4 - Returns: *this.
5 - Throws: nothing.
template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u);
6 - Requires: If the deleter E is not a reference type, a
Assignment of the deleter D from an rvalueDE shall be well-formed and shall not throw an exception. Otherwise the deleter E is a reference type, and assignment of the deleter D from an lvalue E shall be well-formed and shall not throw an exception.unique_ptr<U, E>::pointer shall be implicitly convertible to pointer. [Note: These requirements imply that T and U are complete types. — end note]Remarks: unique_ptr<U, E>::pointer shall be implicitly convertible to pointer, else this operator shall not participate in overload resolution. U shall not be an array type, else this operator shall not participate in overload resolution.
7 - Effects: reset(u.release()) followed by an
moveassignment fromu's deleter to this deleterstd::forward<E>(u.get_deleter()).If either D or E is a reference type, then the referenced lvalue deleter participates in the move assignment.8 - Postconditions: This unique_ptr now owns the pointer which u owned, and u no longer owns it, u.get() == nullptr.
unique_ptr& operator=(nullptr_t
});11 - Effects: reset().
12 - Postcondition: get() ==
0nullptr13 - Returns: *this.
14 - Throws: nothing.
typename add_lvalue_reference<T>::type operator*() const;
1 - Requires: get() !=
0nullptr.2 - Returns: *get().
3 - Throws: nothing.pointer operator->() const;
4 - Requires: get() !=
0nullptr.5 - Returns: get().
6 - Throws: nothing.
7 - Note: use typically requires that T be a complete type.
explicit operator bool() const;
12 - Returns: get() !=
0nullptr.13 - Throws: nothing.
pointer release();
1 - Postcondition: get() ==
0nullptr.2 - Returns: The value get() had at the start of the call to release.
3 - Throws: nothing.
void swap(unique_ptr& u);
8 - Requires: The deleter D shall
besatisfy the Swappable requirements and shall not throw an exception under swap.9 - Effects:
The stored pointers of this and u are exchanged. The stored deleters are swap'd (unqualified)Invokes swap on the stored pointers and on the stored deleters of *this and u.
// assignment
unique_ptr& operator=(unique_ptr&& u);
unique_ptr& operator=(unspecified-pointer-typenullptr_t);
T& operator[](size_t i) const;
1 - Requires: i < the size of the array to which the stored pointer points.
2 - Returns: get()[i].
3 - Throws: nothing.
void reset(pointer p = pointer());
void reset(nullptr_t p);1 - Effects: If get() ==
0nullptr there are no effects. Otherwise get_deleter()(get()).
template <class T1, class D1, class T2, class D2>
bool operator==(const unique_ptr<T1, D1>& x, const unique_ptr<T2, D2>& y);2 - Returns: 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);3 - Returns: 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);4 - Returns: 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);5 - Returns: 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: 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.get() >= y.get().
Change Table 40 — Allocator requirements as indicated: [All removed entries are already specified via the NullablePointer requirements]
Table 40 — Allocator requirements Expression Return type Assertion/note
pre-/post-conditionDefault ... ... ... ... pointer d(nullptr)
pointer d = nullptr
const_pointer e(nullptr)
const_pointer e = nullptrd and e are null pointers and need not be dereferenceable.
static_cast<bool>(d) == false,
static_cast<bool>(e) == falsevoid_pointer d(nullptr)
void_pointer d = nullptr
const_void_pointer e(nullptr)
const_void_pointer e = nullptrd and e are null pointers and need not be dereferenceable.
static_cast<bool>(d) == false,
static_cast<bool>(e) == falsepcontextually convertible to boolfalse only if p is a null pointerqcontextually convertible to boolfalse only if q is a null pointerwcontextually convertible to boolfalse only if w is a null pointerzcontextually convertible to boolfalse only if z is a null pointer!pcontextually convertible to booltrue only if p is a null pointer!qcontextually convertible to booltrue only if q is a null pointer!wcontextually convertible to booltrue only if w is a null pointer!zcontextually convertible to booltrue only if z is a null pointer... ... ... ...
Change [allocator.requirements]/4 as indicated [All removed parts are already specified via the NullablePointer requirements. This change additionally ensures that != is now well-defined for the allocator pointer types and fixes therefore issue 1307]:
The X::pointer, X::const_pointer, X::void_pointer, and X::const_void_pointer types shall satisfy the NullablePointer requirements ([nullablepointer.requirements])
of EqualityComparable, DefaultConstructible, CopyConstructible, CopyAssignable, Swappable, and Destructible (20.2.1). No constructor, comparison operator, copy operation, move operation, or swap operation on these types shall exit via an exception.A default-initialized object may have a singular value. A value-initialized object shall compare equal to nullptr. X::pointer and X::const_pointer shall also satisfy the requirements for a random access iterator (24.2).
During reviews of this paper it became controversial how to properly specify the operational semantics of operator*, operator[], and the heterogenous comparison functions. [structure.specifications]/3 doesn't clearly say whether a Returns element (in the absence of the new Equivalent to formula) specifies effects. Further-on it's unclear whether this would allow for such a return expression to exit via an exception, if additionally a Throws:-Nothing element is provided (would the implementor be required to catch those?). To resolve this conflict, any existing Throws element was removed for these operations, which is at least consistent with [unique.ptr.special] and other parts of the standard. The result of this is that we give now implicit support for potentially throwing comparison functions, but not for homogeneous == and !=, which might be a bit surprising.
The author would like to express special thanks to Howard Hinnant and Alisdair Meredith for many detailed improvement suggestions and reviews during several stages of this proposal and to Alberto Ganesh Barbati, Beman Dawes, and Peter Dimov for valuable discussions and improvement suggestions.