unique_ptr
for array does not support cv qualification conversion of actual argumentSection: 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.
#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 byreset
.
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 fromT
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]
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.
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.
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 ofstd::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 toargument pointerspointer
p
to types derived fromT
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
.
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.
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 toT*
, and
T
is not a base class ofV
(without regard to cv-qualifiers),where
V
is the array element type ofU
.void operator()(T* ptr) const;-1- Effects: calls
-2- Remarks: Ifdelete[]
onptr
.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 ofU
(without regard to cv-qualifiers).Revise 20.3.1.3 [unique.ptr.single]/3 as follows:
If the type
remove_reference<D>::type::pointer
exists, thenunique_ptr<T, D>::pointer
shall be a synonym forremove_reference<D>::type::pointer
. Otherwiseunique_ptr<T, D>::pointer
shall be a synonym for. The type
Telement_type*unique_ptr<T, D>::pointer
shall satisfy the requirements ofNullablePointer
(16.4.4.4 [nullablepointer.requirements]).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 offromunique_ptr<T[], D>
unique_ptr<Derived[]>
tounique_ptr<Base[]>
, whereBase
is a base class ofDerived
, fromauto_ptr
, or to or from the non-array forms ofunique_ptr
produce an ill-formed program.Pointers to types derived from
T
are rejected by the constructors, and byreset
.The observers
operator*
andoperator->
are not provided.The indexing observer
operator[]
is provided.The default deleter will call
delete[]
.-2- Descriptions are provided below only for
-3- The template argumentmember functions that have behavior differentmembers that differ from the primary template.T
shall be a complete type.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 topointer
, and
T
is a base class ofU
(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 topointer
, and
U
is an array type, andeither
D
is a reference type andE
is the same type asD
, orD
is not a reference type andE
is implicitly convertible toD
, andeither at least one of
pointer
andunique_ptr<U, E>::pointer
is not a pointer type, orT
is not a base class of the array element type ofU
(without regard to cv-qualifiers).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 topointer
, and
U
is an array type, andeither
D
is a reference type andE
is the same type asD
, orD
is not a reference type andE
is implicitly convertible toD
, andeither at least one of
pointer
andunique_ptr<U, E>::pointer
is not a pointer type, orT
is not a base class of the array element type ofU
(without regard to cv-qualifiers).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: Ifget() == nullptr
there are no effects. Otherwiseget_deleter()(get())
.-2- Postcondition:-?- This function shall not participate in overload resolution unless:get() == p
.
pointer
is a pointer type, and
U*
is implicitly convertible topointer
, and
T
is a base class ofU
(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.