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

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

Priority: 1

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

View all issues with Resolved status.

Discussion:

Addresses US 16

N3290 23.11.1.3.1 [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 23.11.1.3 [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 23.11.1.3.1 [unique.ptr.runtime.ctor] then rejects far too much...

Proposed new wording of N3290 23.11.1.3.1 [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:

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 23.11.1.3.1 [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 pointerargument 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 23.11.1.1.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 23.11.1.2 [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 (20.5.3.3 [nullablepointer.requirements]).

  3. Revise 23.11.1.3 [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 differentmembers that differ from the primary template.

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

  4. Revise 23.11.1.3.1 [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 23.11.1.3.1 [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 23.11.1.3.4 [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.