3210. allocate_shared is inconsistent about removing const from the pointer passed to allocator construct and destroy

Section: [util.smartptr.shared.create] Status: New Submitter: Billy O'Neal III Opened: 2019-05-29 Last modified: 2020-09-06 13:52:31 UTC

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.


I implemented the fix for LWG 3008 and Stephan pointed out there's an inconsistency here for allocate_shared<const T>. [util.smartptr.shared.create] p3 says that the allocator construct call is done without removing cv qualifiers, but [util.smartptr.shared.create] p7.12 says that the 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:

  1. 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.

  2. 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.

  3. Back out allocate_shared's interaction with this part of the allocator model (reverting this part of P0674 and reopening LWG 3008).

  4. 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]

Proposed resolution:

This wording is relative to N4810.

[Drafting note: As the issue submitter prefers option (2), this is wording for that.]

  1. Modify [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:

    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, pv, v) or

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

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