allocate_shared
is inconsistent about removing const
from the pointer
passed to allocator construct
and destroy
Section: 20.3.2.2.7 [util.smartptr.shared.create] Status: New Submitter: Billy O'Neal III Opened: 2019-05-29 Last modified: 2024-10-02
Priority: 3
View other active issues in [util.smartptr.shared.create].
View all other issues in [util.smartptr.shared.create].
View all issues with New status.
Discussion:
I implemented the fix for LWG 3008 and Stephan pointed out there's an inconsistency here
for allocate_shared<const T>
.
destroy
call is done with removed cv qualifiers.
The fallback for allocator_traits::construct
rejects const T*
(since it static_casts
to void*
), so the most likely outcome of attempting to do this today is to fail to compile, which
is a break with C++17.
Our options are:
Fix the allocator model to deal with const
elements somehow, which breaks compatibility
with existing allocators unprepared for const
elements here. We would need to extend the allocator
requirements to allow const T*
to be passed here, and fix our default to const_cast
.
Fix allocate_shared
to remove const
before calling construct
, which
changes the experience for C++17 customers because allocate_shared
constructs a T
instead of a const T
, but not in a way substantially different to edits
P0674 already made here.
Back out allocate_shared
's interaction with this part of the allocator model (reverting
this part of P0674 and reopening LWG 3008).
Go around the problem by prohibiting allocate_shared<const T>
, which breaks
existing C++17 customers.
Billy O'Neal argues that only (2) preserves the design intent P0674 while maintaining compatibility for most allocators and most C++17 customers.
Peter Dimov argues that (1) isn't likely to break enough to matter.[2019-06-16 Priority set to 3 based on reflector discussion]
Previous resolution [SUPERSEDED]:
This wording is relative to N4810.
[Drafting note: As the issue submitter prefers option (2), this is wording for that.]
Modify 20.3.2.2.7 [util.smartptr.shared.create] as indicated:
template<class T, ...> shared_ptr<T> make_shared(args); template<class T, class A, ...> shared_ptr<T> allocate_shared(const A& a, args); template<class T, ...> shared_ptr<T> make_shared_default_init(args); template<class T, class A, ...> shared_ptr<T> allocate_shared_default_init(const A& a, args);-2- Requires: […]
[…] -7- Remarks:
(7.1) — […]
[…]
(7.5) — When a (sub)object of a non-array type
U
is specified to have an initial value ofv
, orU(l...)
, wherel...
is a list of constructor arguments,allocate_shared
shall initialize this (sub)object via the expression
(7.5.1) —
allocator_traits<A2>::construct(a2, pv, v)
or(7.5.2) —
allocator_traits<A2>::construct(a2, pv, l...)
respectively, where
pv
points to storage suitable to hold an object of typeremove_cv_t<U>
anda2
of typeA2
is a rebound copy of the allocatora
passed toallocate_shared
such that itsvalue_type
isremove_cv_t<U>
.
[2024-04-13; Jiang An comments and provides improved wording]
The currently proposed resolution is meaningless, because "(allocated) storage suitable to hold an object of type
remove_cv_t<U>
" is always "storage suitable to hold an object of type U
", and vice versa.
Also, the current specification doesn't seem to specify the type of pv
in the cases of allocator_shared
,
because pv
is merely specified to point some storage instead of an object.
[2024-10-02; will be resolved by issue 3216.]
Proposed resolution:
This wording is relative to N4971.
Modify 20.3.2.2.7 [util.smartptr.shared.create] as indicated:
[Drafting note: As the issue submitter prefers option (2), this is wording for that.]
template<class T, ...> shared_ptr<T> make_shared(args); template<class T, class A, ...> shared_ptr<T> allocate_shared(const A& a, args); template<class T, ...> shared_ptr<T> make_shared_for_overwrite(args); template<class T, class A, ...> shared_ptr<T> allocate_shared_for_overwrite(const A& a, args);-2- Preconditions: […]
[…] -7- Remarks:
(7.1) — […]
[…]
(7.5) — When a (sub)object of a non-array type
U
is specified to have an initial value ofv
, orU(l...)
, wherel...
is a list of constructor arguments,allocate_shared
shall initialize this (sub)object via the expression
(7.5.1) —
allocator_traits<A2>::construct(a2, pv, v)
or(7.5.2) —
allocator_traits<A2>::construct(a2, pv, l...)
respectively, where
pv
has typeremove_cv_t<U>*
and points to storage suitable to hold an object of typeU
anda2
of typeA2
is a rebound copy of the allocator a passed toallocate_shared
such that itsvalue_type
isremove_cv_t<U>
.(7.6) — […]
(7.7) — When a (sub)object of non-array type
U
is specified to have a default initial value,allocate_shared
shall initialize this (sub)object via the expressionallocator_traits<A2>::construct(a2, pv)
, wherepv
has typeremove_cv_t<U>*
and points to storage suitable to hold an object of typeU
anda2
of typeA2
is a rebound copy of the allocator a passed toallocate_shared
such that itsvalue_type
isremove_cv_t<U>
.