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.

### 2118. [CD] `unique_ptr` for array does not support cv qualification conversion of actual argument

Section: 20.3.1.4 [unique.ptr.runtime] Status: Resolved Submitter: Alf P. Steinbach Opened: 2011-12-16 Last modified: 2017-09-07

Priority: 1

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

View all issues with Resolved status.

Discussion:

Addresses US 16

N3290 20.3.1.4.2 [unique.ptr.runtime.ctor] "`unique_ptr` constructors":

> These constructors behave the same as in the primary template except that they do not accept pointer types which are convertible to `pointer`. [Note: One implementation technique is to create private templated overloads of these members. — end note]

This language excludes even `pointer` itself as type for the actual argument.

But of more practical concern is that both Visual C++ 10.0 and MinGW g++ 4.1.1 reject the code below, where only an implicit cv qualification is needed, which cv qualification is supported by the non-array version:

>     #include <memory>
>     using namespace std;

>     struct T {};

>     T* foo() { return new T; }
>     T const* bar() { return foo(); }

>     int main()
>     {
>        unique_ptr< T const >       p1( bar() );        // OK
>        unique_ptr< T const [] >    a1( bar() );        // OK

>        unique_ptr< T const >       p2( foo() );        // OK
>        unique_ptr< T const [] >    a2( foo() );        // ? this is line #15
>     }

The intent seems to be clearly specified in 20.3.1.4 [unique.ptr.runtime]/1 second bullet:

> — Pointers to types derived from `T` are rejected by the constructors, and by `reset`.

But the following language in 20.3.1.4.2 [unique.ptr.runtime.ctor] then rejects far too much...

Proposed new wording of N3290 20.3.1.4.2 [unique.ptr.runtime.ctor] "`unique_ptr` constructors":

> These constructors behave the same as in the primary template except that actual argument pointers `p` to types derived from `T` are rejected by the constructors. [Note: One implementation technique is to create private templated overloads of these members. — end note]

This will possibly capture the intent better, and avoid the inconsistency between the non-array and array versions of `unique_ptr`, by using nearly the exact same phrasing as for the paragraph explaining the intent.

[2012-08-25 Geoffrey Romer comments in c++std-lib-32978]

1. The current P/R seems to intend to support at least two different implementation techniques — additional unusable templates that catch forbidden arguments or replacing existing constructors by templates that ensure ill-formed code inside the template body, when the requirements are not met. It seems unclear whether the current wording allows the second approach, though. It should be considered to allow both strategies or if that is not possible the note should be clearer.

2. The very same problem exists for the `reset` member function, but even worse, because the current specification is more than clear that the deleted `reset` function will catch all cases not equal to `pointer`. It seems confusing at best to have different policies for the constructor and for the `reset` function. In this case, the question in regard to implementation freedom mentioned above is even more important.

3. It's awkward to refer to "the constructors" twice in the same sentence; I suggest revising the sentence as "...except that they do not accept argument pointers `p` to types derived from `T`"

[2012-12-20: Geoffrey Romer comments and provides a revised resolution]

The array specialization of `unique_ptr` differs from the primary template in several ways, including the following:

- `unique_ptr<T[], D>` cannot be constructed from a plain pointer whose type is not exactly `unique_ptr<T[], D>::pointer` or `nullptr_t`.

- `unique_ptr<T[], D>` cannot be constructed from a `unique_ptr<U[], E>&&` unless `U` is exactly `T` and `E` is exactly `D`.

- `unique_ptr<T[], D>` cannot be moveassigned from a `unique_ptr<U[], E>&&` unless `U` is exactly `T` and `E` is exactly `D`.

- `unique_ptr<T[], D>::reset` cannot take an argument whose type is not exactly `unique_ptr<T[], D>::pointer` or `nullptr_t`.

- `default_delete<T[]>` cannot be constructed from a `default_delete<U[]>` unless `U` is exactly `T`.

- `default_delete<T[]>::operator()` cannot be called on a pointer whose type is not exactly `T*`.

The common intent of all these restrictions appears to be to disallow implicit conversions from pointer-to-derived-class to pointer-to-base-class in contexts where the pointer is known to point to an array, because such conversions are inherently unsafe; deleting or subscripting the result of such a conversion leads to undefined behavior (see also CWG 1504). However, these restrictions have the effect of disallowing all implicit conversions in those contexts, including most notably cv-qualification, but also user-defined conversions, and possibly others. This PR narrows all those restrictions, to disallow only unsafe pointer-to-derived to pointer-to-base conversions, while allowing all others.

I removed the nebulous language stating that certain functions "will not accept" certain arguments. Instead I use explicitly deleted template functions, which participate in overload resolution only for pointer-to-derived to pointer-to-base conversions. This is more consistent with the existing text and easier to express correctly than an approach based on declaring certain types of calls to be ill-formed, but may produce inferior compiler diagnostics.

Wherever possible, this PR defines the semantics of template specializations in terms of their differences from the primary template. This improves clarity and minimizes the risk of unintended differences (e.g. LWG 2169, which this PR also fixes). This PR also makes it explicit that the specialization inherits the description of all members, not just member functions, from the primary template and, in passing, clarifies the default definition of pointer in the specialization.

This resolution only disallows pointer-to-derived to pointer-to-base conversions between ordinary pointer types; if user-defined pointer types provide comparable conversions, it is their responsibility to ensure they are safe. This is consistent with C++'s general preference for expressive power over safety, and for assuming the user knows what they're doing; furthermore, enforcing such a restriction on user-defined types appears to be impractical without cooperation from the user.

The "base class without regard to cv-qualifiers" language is intended to parallel the specification of `std::is_base_of`.

Jonathan Wakely has a working implementation of this PR patched into libstdc++.

Previous resolution:

> This wording is relative to the FDIS.

> Change 20.3.1.4.2 [unique.ptr.runtime.ctor] as indicated:

> >     explicit unique_ptr(pointer p) noexcept;
> >     unique_ptr(pointer p, see below d) noexcept;
> >     unique_ptr(pointer p, see below d) noexcept;

> > > These constructors behave the same as in the primary template except that they do not accept pointer types which are convertible to `pointer` argument pointers `p` to types derived from `T` are rejected by the constructors. [Note: One implementation technique is to create private templated overloads of these members. — end note]

[2014-02, Issaquah]

GR: want to prevent unsafe conversions. Standard is inconsistent how it does this. for `reset()` has deleted function template capturing everything except the known-safe cases. Other functions use SFINAE. Main reason this is hard is that `unique_ptr` supports fancy pointers. Have to figure out how to handle them and what requirements to put on them. Requirements are minimal, not even required to work with `pointer_traits`.

STL surprised `pointer_traits` doesn't work

GR: ways to get fancy pointers to work is to delegate responsibility for preventing unsafe conversions to the fancy pointers themselves. Howard doesn't like that, he wants even fancy pointers to be prevented from doing unsafe conversions in `unique_ptr` contexts.

AM: Howard says `unique_ptr` was meant to be very very safe under all conditions, this open a hole in that. Howard wants to eke forward and support more, but not if we open any holes in type safety.

GR: do we need to be typesafe even for fancy types with incorrect `pointer_traits`?

AM: that would mean it's only unsafe for people who lie by providing a broken specialization of `pointer_traits`

GR: probably can't continue with ambiguity between using SFINAE and ill-formedness. Would appreciate guidance on direction used for that.

STL: difference is observable in convertibility using type traits.

STL: for `reset()` which doesn't affect convertibility ill-formed allows `static_assert`, better diagnostic. For assignment it's detectable and has traits, constraining them is better.

EN: I strongly prefer constraints than `static_asserts`

STL: if we could rely on `pointer_traits` that might be good. Alternatively could we add more machinery to deleter? make deleter say conversions are allowed, otherwise we lock down all conversions. basically want to know if converting `U` to `T` is safe.

Previous resolution [SUPERSEDED]:

> This wording is relative to N3485.

> 1. Revise 20.3.1.2.3 [unique.ptr.dltr.dflt1] as follows

>    >     namespace std {
>    >       template <class T> struct default_delete<T[]> {
>    >         constexpr default_delete() noexcept = default;
>    >         template <class U> default_delete(const default_delete<U>&) noexcept;
>    >         void operator()(T*) const;
>    >         template <class U> void operator()(U*) const = delete;
>    >       };
>    >     }

>    -?- Descriptions are provided below only for member functions that have behavior different from the primary template.

>    >     template <class U> default_delete(const default_delete<U>&) noexcept;

>    > > -?- This constructor behaves the same as in the primary template except that it shall not participate in overload resolution unless:

>    > > - `U` is an array type, and

>    > > - `V*` is implicitly convertible to `T*`, and

>    > > - `T` is not a base class of `V` (without regard to cv-qualifiers),

>    > > where `V` is the array element type of `U`.

>    > >     void operator()(T* ptr) const;

>    > > > -1- Effects: calls `delete[]` on `ptr`.

>    > > > -2- Remarks: If `T` is an incomplete type, the program is ill-formed.

>    > >     template <class U> void operator()(U*) const = delete;

>    > > > -?- Remarks: This function shall not participate in overload resolution unless `T` is a base class of `U` (without regard to cv-qualifiers).

> 2. Revise 20.3.1.3 [unique.ptr.single]/3 as follows:

>    > 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 `Telement_type*`. The type `unique_ptr<T, D>::pointer` shall satisfy the requirements of `NullablePointer` (16.4.4.4 [nullablepointer.requirements]).

> 3. Revise 20.3.1.4 [unique.ptr.runtime] as follows:

>    >     namespace std {
>    >       template <class T, class D> class unique_ptr<T[], D> {
>    >       public:
>    >         typedef see below pointer;
>    >         typedef T element_type;
>    >         typedef D deleter_type;

>    >         // 20.7.1.3.1, constructors
>    >         constexpr unique_ptr() noexcept;
>    >         explicit unique_ptr(pointer p) noexcept;
>    >         template <class U> explicit unique_ptr(U* p) = delete;
>    >         unique_ptr(pointer p, see below d) noexcept;
>    >         template <class U> unique_ptr(U* p, see below d) = delete;
>    >         unique_ptr(pointer p, see below d) noexcept;
>    >         template <class U> unique_ptr(U* p, see below d) = delete;
>    >         unique_ptr(unique_ptr&& u) noexcept;
>    >         constexpr unique_ptr(nullptr_t) noexcept : unique_ptr() { }
>    >         template <class U, class E> unique_ptr(unique_ptr<U, E>&& u) noexcept;

>    >         // destructor
>    >         ~unique_ptr();

>    >         // assignment
>    >         unique_ptr& operator=(unique_ptr&& u) noexcept;
>    >         template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;
>    >         unique_ptr& operator=(nullptr_t) noexcept;

>    >         // 20.7.1.3.2, observers
>    >         T& operator[](size_t i) const;
>    >         pointer get() const noexcept;
>    >         deleter_type& get_deleter() noexcept;
>    >         const deleter_type& get_deleter() const noexcept;
>    >         explicit operator bool() const noexcept;

>    >         // 20.7.1.3.3 modifiers
>    >         pointer release() noexcept;
>    >         void reset(pointer p = pointer()) noexcept;
>    >         void reset(nullptr_t) noexcept;
>    >         template <class U> void reset(U*) = delete;
>    >         void swap(unique_ptr& u) noexcept;

>    >         // disable copy from lvalue
>    >         unique_ptr(const unique_ptr&) = delete;
>    >         unique_ptr& operator=(const unique_ptr&) = delete;
>    >       };
>    >     }

>    > -1- A specialization for array types is provided with a slightly altered interface.

>    > - Conversions between different types of `unique_ptr<T[], D>`from `unique_ptr<Derived[]>` to `unique_ptr<Base[]>`, where `Base` is a base class of `Derived`, from `auto_ptr`, or to or from the non-array forms of `unique_ptr` produce an ill-formed program.

>    > - Pointers to types derived from `T` are rejected by the constructors, and by `reset`.

>    > - The observers `operator*` and `operator->` are not provided.

>    > - The indexing observer `operator[]` is provided.

>    > - The default deleter will call `delete[]`.

>    > -2- Descriptions are provided below only for member functions that have behavior different members that differ from the primary template.

>    > -3- The template argument `T` shall be a complete type.

> 4. Revise 20.3.1.4.2 [unique.ptr.runtime.ctor] as follows:

>    >     explicit unique_ptr(pointer p) noexcept;
>    >     unique_ptr(pointer p, see below d) noexcept;
>    >     unique_ptr(pointer p, see below d) noexcept;
>    >     template <class U> explicit unique_ptr(U* p) = delete;
>    >     template <class U> unique_ptr(U* p, see below d) = delete;
>    >     template <class U> unique_ptr(U* p, see below d) = delete;

>    > > These constructors behave the same as in the primary template except that they do not accept pointer types which are convertible to pointer. [Note: One implementation technique is to create private templated overloads of these members. — end note]These constructors shall not participate in overload resolution unless:

>    > > - `pointer` is a pointer type, and

>    > > - `U*` is implicitly convertible to `pointer`, and

>    > > - `T` is a base class of `U` (without regard to cv-qualifiers).

>    > > The type of `d` is determined as in the corresponding non-deleted constructors.

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

>    > > -?- This constructor behaves the same as in the primary template, except that it shall not participate in overload resolution unless:

>    > > - `unique_ptr<U, E>::pointer` is implicitly convertible to `pointer`, and

>    > > - `U` is an array type, and

>    > > - either `D` is a reference type and `E` is the same type as `D`, or `D` is not a reference type and `E` is implicitly convertible to `D`, and

>    > > - either at least one of `pointer` and `unique_ptr<U, E>::pointer` is not a pointer type, or `T` is not a base class of the array element type of `U` (without regard to cv-qualifiers).

> 5. Insert a new sub-clause following 20.3.1.4.2 [unique.ptr.runtime.ctor] as follows:

>    ?? `unique_ptr` assignment [unique.ptr.runtime.asgn]

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

>    > > -?- This operator behaves the same as in the primary template, except that it shall not participate in overload resolution unless:

>    > > - `unique_ptr<U, E>::pointer` is implicitly convertible to `pointer`, and

>    > > - `U` is an array type, and

>    > > - either `D` is a reference type and `E` is the same type as `D`, or `D` is not a reference type and `E` is implicitly convertible to `D`, and

>    > > - either at least one of `pointer` and `unique_ptr<U, E>::pointer` is not a pointer type, or `T` is not a base class of the array element type of `U` (without regard to cv-qualifiers).

> 6. Revise 20.3.1.4.5 [unique.ptr.runtime.modifiers] as follows:

>    >     void reset(pointer p = pointer()) noexcept;
>    >     void reset(nullptr_t p) noexcept;
>    >     template <class U> void reset(U*) = delete;

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

>    > > -2- Postcondition: `get() == p`.

>    > > -?- This function shall not participate in overload resolution unless:

>    > > - `pointer` is a pointer type, and

>    > > - `U*` is implicitly convertible to `pointer`, and

>    > > - `T` is a base class of `U` (without regard to cv-qualifiers).

[2014-06 Rapperswil]

Discussion of N4042 and general agreement that this paper resolves the substance of this issue and should be adopted with minor edits. Geoffrey Romer will provide an updated paper.

[2014-06 post-Rapperswil]

As described in N4089.

[2014-11-07 Urbana]

Resolved by N4089

Proposed resolution:

See proposed wording in N4089.