This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.

### 834. `unique_ptr::pointer` requirements underspecified

Section: 20.3.1.3 [unique.ptr.single] Status: Resolved Submitter: Daniel Krügler Opened: 2008-05-14 Last modified: 2016-01-28

Priority: Not Prioritized

View other active issues in [unique.ptr.single].

View all other issues in [unique.ptr.single].

View all issues with Resolved status.

Discussion:

Issue 673 (including recent updates by 821) proposes a useful extension point for `unique_ptr` by granting support for an optional `deleter_type::pointer` to act as pointer-like replacement for `element_type*` (In the following: `pointer`).

Unfortunately no requirements are specified for the type `pointer` which has impact on at least two key features of `unique_ptr`:

1. Operational fail-safety.
2. (Well-)Definedness of expressions.

The `unique_ptr` specification makes great efforts to require that essentially all operations cannot throw and therefore adds proper wording to the affected operations of the deleter as well. If user-provided `pointer`-emulating types ("smart pointers") will be allowed, either all throw-nothing clauses have to be replaced by weaker "An exception is thrown only if `pointer`'s {op} throws an exception"-clauses or it has to be said explicitly that all used operations of `pointer` are required not to throw. I understand the main focus of `unique_ptr` to be as near as possible to the advantages of native pointers which cannot fail and thus strongly favor the second choice. Also, the alternative position would make it much harder to write safe and simple template code for `unique_ptr`. Additionally, I assume that a general statement need to be given that all of the expressions of `pointer` used to define semantics are required to be well-formed and well-defined (also as back-end for 762).

[ Sophia Antipolis: ]

> Howard: We maybe need a core concept `PointerLike`, but we don't need the arithmetic (see `shared_ptr` vs. `vector<T>::iterator`.

> Howard will go through and enumerate the individual requirements wrt. `pointer` for each member function.

[ 2009-07 Frankfurt: ]

> Move to Ready.

[ 2009-10-15 Alisdair pulls from Ready: ]

> I hate to pull an issue out of Ready status, but I don't think 834 is fully baked yet.

> For reference the proposed resolution is to add the following words:

> > `unique_ptr<T, D>::pointer`'s operations shall be well-formed, shall have well defined behavior, and shall not throw exceptions.

> This leaves me with a big question : which operations?

> Are all pointer operations required to be nothrow, including operations that have nothing to do with interactions with `unique_ptr`? This was much simpler with concepts where we could point to operations within a certain concept, and so nail down the interactions.

[ 2009-10-15 Daniel adds: ]

> I volunteer to prepare a more fine-grained solution, but I would like to ask for feedback that helps me doing so. If this question is asked early in the meeting I might be able to fix it within the week, but I cannot promise that now.

[ 2009-10 Santa Cruz: ]

> Leave in open. Daniel to provide wording as already suggested.

[ 2009-12-22 Daniel provided wording and rationale. ]

[ 2010 Pittsburgh: Moved to NAD Editorial. Rationale added below. ]

Rationale:

The here proposed resolution has considerable overlap with the requirements that are used in the allocator requirements.

This might be a convincing argument to isolate the common subset into one requirement. The reason I did not do that is basically because we might find out that they are either over-constraining or under-constraining at this late point of specification. Note also that as a result of the idea of a general requirement set I added the requirement:

> A default-initialized object may have a singular value

even though this does not play a relevant role for `unique_ptr`.

One further characteristics of the resolution is that availability of relational operators of `unique_ptr<T, D>::pointer` is not part of the basic requirements, which is in sync with the allocator requirements on pointer-like (this means that `unique_ptr` can hold a `void_pointer` or `const_void_pointer`).

Solved by N3073.

Proposed resolution:

1. Change 20.3.1.3 [unique.ptr.single] p. 1 as indicated: [The intent is to replace the coupling between `T*` and the deleter's `operator()` by a coupling between `unique_ptr<T, D>::pointer` and this `operator()`]

   > 1 - The default type for the template parameter `D` is `default_delete`. A client-supplied template argument `D` shall be a function pointer or functor for which, given a value `d` of type `D` and a pointer value `ptr` of type `T* 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. Change 20.3.1.3 [unique.ptr.single] p. 3 as indicated:

   > 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 satisfy the requirements of `EqualityComparable`, `DefaultConstructible`, `CopyConstructible` (Table 34) and, `CopyAssignable` (Table 36), `Swappable`, and `Destructible` (16.4.4.2 [utility.arg.requirements]). A default-initialized object may have a singular value. A value-initialized object produces the null value of the type. The null value shall be equivalent only to itself. An object of this type can be copy-initialized with a value of type `nullptr_t`, compared for equality with a value of type `nullptr_t`, and assigned a value of type `nullptr_t`. The effect shall be as if a value-initialized object had been used in place of the null pointer constant. An object `p` of this type can be contextually converted to `bool`. The effect shall be as if `p != nullptr` had been evaluated in place of `p`. No operation on this type which is part of the above mentioned requirements shall exit via an exception.

   > [Note: Given an allocator type `X` (16.4.4.6 [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 note]

   > In addition to being available via inclusion of the `<utility>` header, the `swap` function template in 22.2.2 [utility.swap] is also available within the definition of `unique_ptr`'s `swap` function.

3. Change 20.3.1.3.2 [unique.ptr.single.ctor] p. 2+3 as indicated: [The first change ensures that we explicitly say, how the stored pointer is initialized. This is important for a `constexpr` function, because this may make a difference for user-defined pointer-like types]

   >     constexpr unique_ptr();

   > > ...

   > > 2 - Effects: Constructs a `unique_ptr` which owns nothing, value-initializing the stored pointer.

   > > 3 - Postconditions: `get() == 0 nullptr`.

4. Change 20.3.1.3.2 [unique.ptr.single.ctor] p. 6+7 as indicated: [This is a step-by-fix to ensure consistency to the changes of N2976]

   >     unique_ptr(pointer p);

   > > ...

   > > 6 - Effects: Constructs a `unique_ptr` which owns `p`, initializing the stored pointer with `p`.

   > > 7 - Postconditions: `get() == p`. `get_deleter()` returns a reference to a default constructed value-initialized deleter `D`.

5. Insert a new effects clause in 20.3.1.3.2 [unique.ptr.single.ctor] just before p. 14: [The intent is to fix the current lack of specification in which way the stored pointer is initialized]

   >     unique_ptr(pointer p, implementation-defined see below d1);
   >     unique_ptr(pointer p, implementation-defined see below d2);

   > > ...

   > > Effects: Constructs a `unique_ptr` which owns `p`, initializing the stored pointer with `p` and the 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`.

6. Change 20.3.1.3.2 [unique.ptr.single.ctor] p. 18+22 as indicated: [The intent is to clarify that the moved-from source must contain a null pointer, there is no other choice left]

   >     unique_ptr(unique_ptr&& u);

   > > [..]

   > > 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.

   >     template <class U, class E> unique_ptr(unique_ptr<U, E>&& u);

   > > [..]

   > > 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()`.

7. Change 20.3.1.3.2 [unique.ptr.single.ctor] p. 20 as indicated: [With the possibility of user-defined pointer-like types the implication does only exist, if those are built-in pointers. Note that this change should also be applied with the acceptance of 950]

   >     template <class U, class E> unique_ptr(unique_ptr<U, E>&& u);

   > > 20 - Requires: If `D` 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. 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]

8. Change 20.3.1.3.3 [unique.ptr.single.dtor] p. 2 as indicated:

   >     ~unique_ptr();

   > > ...

   > > 2 - Effects: If `get() == 0 nullptr` there are no effects. Otherwise `get_deleter()(get())`.

9. Change 20.3.1.3.4 [unique.ptr.single.asgn] p. 3+8 as indicated: [The intent is to clarify that the moved-from source must contain a null pointer, there is no other choice left]

   >     unique_ptr& operator=(unique_ptr&& u);

   > > [..]

   > > 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]

   >     template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u);

   > > [..]

   > > 8 - Postconditions: This `unique_ptr` now owns the pointer which `u` owned, and `u` no longer owns it, `u.get() == nullptr`.

10. Change 20.3.1.3.4 [unique.ptr.single.asgn] p. 6 as indicated: [With the possibility of user-defined pointer-like types the implication does only exist, if those are built-in pointers. Note that this change should also be applied with the acceptance of 950]

    >     template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u);

    > > [..]

    > > 6 - Requires: Assignment of the deleter `D` from an rvalue `D` 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]

11. Change 20.3.1.3.4 [unique.ptr.single.asgn] before p. 11 and p. 12 as indicated: [The first change is a simple typo fix]

    >     unique_ptr& operator=(nullptr_t});

    > > 11 - Effects: `reset()`.

    > > 12 - Postcondition: `get() == 0 nullptr`

12. Change 20.3.1.3.5 [unique.ptr.single.observers] p. 1+4+12 as indicated:

    >     typename add_lvalue_reference<T>::type operator*() const;

    > > 1 - Requires: `get() != 0 nullptr`. The variable definition `add_lvalue_reference<T>::type t = *get()` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > [..]

    >     pointer operator->() const;

    > > 4 - Requires: `get() != 0 nullptr`.

    > > [..]

    >     explicit operator bool() const;

    > > 12 - Returns: `get() != 0nullptr`.

13. Change 20.3.1.3.6 [unique.ptr.single.modifiers] p. 1 as indicated:

    >     pointer release();

    > > 1 - Postcondition: `get() == 0 nullptr`.

14. Change 20.3.1.3.6 [unique.ptr.single.modifiers] p. 9 as indicated: [The intent is to ensure that potentially user-defined swaps are used. A side-step fix and harmonization with the specification of the the deleter is realized. Please note the additional requirement in bullet 2 of this proposed resolution regarding the availability of the generic `swap` templates within the member `swap` function.]

    >     void swap(unique_ptr& u);

    > > 8 - Requires: The deleter `D` shall be `Swappable` and shall not throw an exception under `swap`.

    > > 9 - Effects: The stored pointers of `*this` and `u` are exchanged by an unqualified call to non-member `swap`. The stored deleters are `swap`'d (unqualified) exchanged by an unqualified call to non-member `swap`.

15. Change 20.3.1.4.4 [unique.ptr.runtime.observers] p. 1 as indicated:

    >     T& operator[](size_t i) const;

    > > Requires: `i <` the size of the array to which the stored pointer points. The variable definition `T& t = get()[i]` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

16. Change 20.3.1.4.5 [unique.ptr.runtime.modifiers] p. 1 as indicated:

    >     void reset(pointer p = pointer());
    >     void reset(nullptr_t p);

    > > 1 - Effects: If `get() == 0 nullptr` there are no effects. Otherwise `get_deleter()(get())`.

17. Change 20.3.1.6 [unique.ptr.special] as indicated: [We don't add the relational operators to the basic requirement set, therefore we need special handling here]

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

    > > Requires: The variable definition `bool b = x.get() == y.get();` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > 2 - Returns: `x.get() == y.get()`.

    > > Throws: nothing.

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

    > > Requires: The variable definition `bool b = x.get() != y.get();` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > 3 - Returns: `x.get() != y.get()`.

    > > Throws: nothing.

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

    > > Requires: The variable definition `bool b = x.get() < y.get()`; shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > 4 - Returns: `x.get() < y.get()`.

    > > Throws: nothing.

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

    > > Requires: The variable definition `bool b = x.get() <= y.get();` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > 5 - Returns: `x.get() <= y.get()`.

    > > Throws: nothing.

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

    > > Requires: The variable definition `bool b = x.get() > y.get();` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > 6 - Returns: `x.get() > y.get()`.

    > > Throws: nothing.

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

    > > Requires: The variable definition `bool b = x.get() >= y.get();` shall be well formed, shall have well-defined behavior, and shall not exit via an exception.

    > > 7 - Returns: `x.get() >= y.get()`.

    > > Throws: nothing.