3216. Rebinding the allocator before calling construct/destroy in allocate_shared

Section: 20.3.2.2.7 [util.smartptr.shared.create] Status: Tentatively Ready Submitter: Billy O'Neal III Opened: 2019-06-11 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 Tentatively Ready status.

Discussion:

The new allocate_shared wording says we need to rebind the allocator back to T's type before we can call construct or destroy, but this is suboptimal (might make extra unnecessary allocator copies), and is inconsistent with the containers' behavior, which call allocator construct on whatever T they want. (For example, std::list<T, alloc<T>> rebinds to alloc<_ListNode<T>>, but calls construct(T*) without rebinding back)

It seems like we should be consistent with the containers and not require a rebind here. PR would look something like this, relative to N4810; I'm still not super happy with this wording because it looks like it might be saying a copy of the allocator must be made we would like to avoid…

[2019-07 Issue Prioritization]

Priority to 3 after discussion on the reflector.

Previous resolution [SUPERSEDED]:

This wording is relative to N4810.

  1. Modify 20.3.2.2.7 [util.smartptr.shared.create] as indicated:

    [Drafting note: The edits to change pv to pu were suggested by Jonathan Wakely (thanks!). This wording also has the remove_cv_t fixes specified by LWG 3210 — if that change is rejected some of those have to be stripped here.]

    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:

    1. (7.1) — […]

    2. […]

    3. (7.5) — When a (sub)object of a non-array type U is specified to have an initial value of v, or U(l...), where l... is a list of constructor arguments, allocate_shared shall initialize this (sub)object via the expression

      1. (7.5.1) — allocator_traits<A2>::construct(a2, pvu, v) or

      2. (7.5.2) — allocator_traits<A2>::construct(a2, pvu, l...)

      respectively, where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    4. (7.6) — […]

    5. (7.7) — When a (sub)object of non-array type U is specified to have a default initial value, allocate_shared shall initializes this (sub)object via the expression allocator_traits<A2>::construct(a2, pvu), where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    6. […]

    7. (7.12) — When a (sub)object of non-array type U that was initialized by allocate_shared is to be destroyed, it is destroyed via the expression allocator_traits<A2>::destroy(a2, pvu) where pvu is a pointer of type remove_cv_t<U>* pointsing to that object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

[2024-08-23; Jonathan provides updated wording]

make_shared_default_init and allocate_shared_default_init were renamed by P1973R1 so this needs a rebase. The edit to (7.11) is just for consistency, so that pv is always void* and pu is remove_cv_t<U>*. Accepting this proposed resolution would also resolve issue 3210.

[2024-10-02; Reflector poll]

Set status to Tentatively Ready after six votes in favour during reflector poll.

Proposed resolution:

This wording is relative to N4988.

  1. 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_for_overwrite(args);
    template<class T, class A, ...>
      shared_ptr<T> allocate_shared_for_overwrite(const A& a, args);
    

    -2- Preconditions: […]

    […]

    -7- Remarks:

    1. (7.1) — […]

    2. […]

    3. (7.5) — When a (sub)object of a non-array type U is specified to have an initial value of v, or U(l...), where l... is a list of constructor arguments, allocate_shared shall initialize this (sub)object via the expression

      1. (7.5.1) — allocator_traits<A2>::construct(a2, pvu, v) or

      2. (7.5.2) — allocator_traits<A2>::construct(a2, pvu, l...)

      respectively, where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    4. (7.6) — […]

    5. (7.7) — When a (sub)object of non-array type U is specified to have a default initial value, allocate_shared shall initializes this (sub)object via the expression allocator_traits<A2>::construct(a2, pvu), where pvu is a pointer of type remove_cv_t<U>* pointsing to storage suitable to hold an object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.

    6. […]

    7. [Drafting note: Issue 4024 will add make_shared_for_overwrite and allocate_shared_for_overwrite to (7.11) but that doesn't conflict with this next edit.]

      (7.11) — When a (sub)object of non-array type U that was initialized by make_shared is to be destroyed, it is destroyed via the expression pvu->~U() where pvu points to that object of type U.

    8. (7.12) — When a (sub)object of non-array type U that was initialized by allocate_shared is to be destroyed, it is destroyed via the expression allocator_traits<A2>::destroy(a2, pvu) where pvu is a pointer of type remove_cv_t<U>* pointsing to that object of type remove_cv_t<U> and a2 of type A2 is a potentially rebound copy of the allocator a passed to allocate_shared such that its value_type is remove_cv_t<U>.