4144. Disallow unique_ptr<T&, D>

Section: 20.3.1.3.1 [unique.ptr.single.general] Status: New Submitter: Jonathan Wakely Opened: 2024-08-30 Last modified: 2024-08-31

Priority: Not Prioritized

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

View all issues with New status.

Discussion:

It seems that we currently allow nonsensical specializations of unique_ptr such as unique_ptr<int&, D> and unique_ptr<void()const, D> (a custom deleter that defines D::pointer is needed, because otherwise the pointer type would default to invalid types like int&* or void(*)()const). There seems to be no reason to support these "unique pointer to reference" and "unique pointer to abominable function type" specializations, or any specialization for a type that you couldn't form a raw pointer to.

Prior to C++17, the major library implementations rejected such specializations as a side effect of the constraints for the unique_ptr(auto_ptr<U>&&) constructor being defined in terms of is_convertible<U*, T*>. This meant that overload resolution for any constructor of unique_ptr would attempt to form the type T* and fail if that was invalid. With the removal of auto_ptr in C++17, that constructor was removed and now unique_ptr<int&, D> can be instantiated (assuming any zombie definition of auto_ptr is not enabled by the library). This wasn't intentional, but just an accident caused by not explicitly forbidding such types.

Discussion on the LWG reflector led to near-unanimous support for explicitly disallowing these specializations for non-pointable types.

Proposed resolution:

This wording is relative to N4988.

  1. Modify 20.3.1.3.1 [unique.ptr.single.general] as indicated:

    -?- A program that instantiates the definition of unique_ptr<T, D> is ill-formed if T* is an invalid type.
    [Note: This prevents the intantiation of specializations such as unique_ptr<T&, D> and unique_ptr<int() const, D>. — end note]

    -1- The default type for the template parameter D is default_delete. A client-supplied template argument D shall be a function object type (22.10 [function.objects]), lvalue reference to function, or lvalue reference to function object type for which, given a value d of type D and a value ptr of type unique_ptr<T, D>::pointer, the expression d(ptr) is valid and has the effect of disposing of the pointer as appropriate for that deleter.

    -2- If the deleter’s type D is not a reference type, D shall meet the Cpp17Destructible requirements (Table 35).

    -3- If the qualified-id remove_reference_t<D>::pointer is valid and denotes a type (13.10.3 [temp.deduct]), then unique_ptr<T, D>::pointer shall be a synonym for remove_reference_t<D>::pointer. Otherwise unique_ptr<T, D>::pointer shall be a synonym for element_type*. The type unique_ptr<T, D>::pointer shall meet the Cpp17NullablePointer requirements (Table 36).

    -4- [Example 1:  Given an allocator type X (16.4.4.6.1 [allocator.requirements.general]) and letting A be a synonym for allocator_traits<X>, the types A::pointer, A::const_pointer, A::void_pointer, and A::const_void_pointer may be used as unique_ptr<T, D>::pointer. — end example]