This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.

### 2070. `allocate_shared` should use `allocator_traits<A>::construct`

Section: 20.3.2.2.7 [util.smartptr.shared.create] Status: Resolved Submitter: Jonathan Wakely Opened: 2011-07-11 Last modified: 2017-07-16

Priority: 2

View other active issues in [util.smartptr.shared.create].

View all other issues in [util.smartptr.shared.create].

View all issues with Resolved status.

Discussion:

20.3.2.2.7 [util.smartptr.shared.create] says:

> -2- Effects: Allocates memory suitable for an object of type `T` and constructs an object in that memory via the placement new expression `::new (pv) T(std::forward<Args>(args)...)`. The template `allocate_shared` uses a copy of a to allocate memory. If an exception is thrown, the functions have no effect.

This explicitly requires placement new rather than using `allocator_traits<A>::construct(a, (T*)pv, std::forward<Args>(args)...)` In most cases that would result in the same placement new expression, but would allow more control over how the object is constructed e.g. using `scoped_allocator_adaptor` to do uses-allocator construction, or using an allocator declared as a friend to construct objects with no public constructors.

[2011-08-16 Bloomington:]

Agreed to fix in principle, but believe that `make_shared` and `allocate_shared` have now diverged enough that their descriptions should be separated. Pablo and Stefanus to provide revised wording.

Daniel's (old) proposed resolution:

> This wording is relative to the FDIS.

> 1. Change the following paragraphs of 20.3.2.2.7 [util.smartptr.shared.create] as indicated (The suggested removal of the last sentence of p1 is not strictly required to resolve this issue, but is still recommended, because it does not say anything new but may give the impression that it says something new):

>    >     template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
>    >     template<class T, class A, class... Args>
>    >       shared_ptr<T> allocate_shared(const A& a, Args&&... args);

>    > > -1- Requires: For the template `make_shared`, t The expression `::new (pv) T(std::forward<Args>(args)...)`, where `pv` has type `void*` and points to storage suitable to hold an object of type `T`, shall be well formed. For the template `allocate_shared`, the expression `allocator_traits<A>::construct(a, pt, std::forward<Args>(args)...)`, where `pt` has type `T*` and points to storage suitable to hold an object of type `T`, shall be well formed. `A` shall be an allocator ([allocator.requirements]). The copy constructor and destructor of `A` shall not throw exceptions.

>    > > -2- Effects: Allocates memory suitable for an object of type `T` and constructs an object in that memory. The template `make_shared` constructs the object via the placement new expression `::new (pv) T(std::forward<Args>(args)...)`. The template `allocate_shared` uses a copy of `a` to allocate memory and constructs the object by calling `allocator_traits<A>::construct(a, pt, std::forward<Args>(args)...)`. If an exception is thrown, the functions have no effect.

>    > > -3- Returns: A `shared_ptr` instance that stores and owns the address of the newly constructed object of type `T`.

>    > > -4- Postconditions: `get() != 0 && use_count() == 1`

>    > > -5- Throws: `bad_alloc`, or, for the template `make_shared`, an exception thrown from the constructor of `T`, or, for the template `allocate_shared`, an exception thrown from `A::allocate` or from `allocator_traits<A>::construct` from the constructor of `T`.

>    > > -6- Remarks: Implementations are encouraged, but not required, to perform no more than one memory allocation. [ Note: This provides efficiency equivalent to an intrusive smart pointer. — end note ]

>    > > -7- [ Note: These functions will typically allocate more memory than `sizeof(T)` to allow for internal bookkeeping structures such as the reference counts. — end note ]

[2011-12-04: Jonathan and Daniel improve wording]

See also c++std-lib-31796

[2013-10-13, Ville]

This issue is related to 2089.

[2014-02-15 post-Issaquah session : move to Tentatively NAD]

STL: This takes an allocator, but then ignores its construct. That's squirrely.

Alisdair: The convention is when you take an allocator, you use its construct.

STL: 24.2.2.1 [container.requirements.general]/3, argh! This fills me with despair, but I understand it now.

STL: Ok, this is some cleanup.

STL: You're requiring `b` to be of type `A` and not being rebound, is that an overspecification?

Pablo: Good point. Hmm, that's only a requirement on what must be well-formed.

STL: If it's just a well-formed requirement, then why not just use a directly?

Pablo: Yeah, the well-formed requirement is overly complex. It's not a real call, we could just use a directly. It makes it harder to read.

Alisdair: `b` should be an allocator in the same family as `a`.

Pablo: This is a well-formed requirement, I wonder if it's the capital A that's the problem here. It doesn't matter here, this is way too much wording.

Alisdair: It's trying to tie the constructor arguments into the allocator requirements.

Pablo: `b` could be struck, that's a runtime quality. The construct will work with anything that's in the family of `A`.

Alisdair: The important part is the `forward` of `Args`.

Pablo: `A` must be an allocator, and `forward` `Args` must work with that.

Alisdair: First let's nail down `A`.

Pablo: Then replace `b` with `a`, and strike the rest.

STL: You need `pt`'s type, at least.

Pablo: There's nothing to be said about runtime constraints here, this function doesn't even take a `pt`.

STL: Looking at the Effects, I believe `b` is similarly messed up, we can use `a2` to construct an object.

Alisdair: Or any allocator in the family of `a`.

STL: We say this stuff for the deallocate too, it should be lifted up.

STL: "owns the address" is weird.

Alisdair: shared_ptr owns pointers, although it does sound funky.

Walter: "to destruct" is ungrammatical.

STL: "When ownership is given up" is not what we usually say.

Alisdair: I think the Returns clause is the right place to say this.

STL: The right place to say this is `shared_ptr`'s dtor, we don't want to use Core's "come from" convention.

Alisdair: I'm on the hook to draft cleaner wording.

[2015-10, Kona Saturday afternoon]

AM: I was going to clean up the wording, but haven't done it yet.
Defer until we have new wording.

[2016-03, Jacksonville]

Alisdair: we need to figure out whether we should call construct or not; major implementation divergence
STL: this does not grant friendship, does it?
Jonathan: some people want it.
Thomas: scoped allocator adapter should be supported, so placement new doesn't work
Alisdair: this makes the make_ functions impossible
Thomas: you don't want to use those though.
Alisdair: but people use that today, at Bloomberg
Alisdair: and what do we do about fancy pointers?
Jonathan: we constrain it to only non-fancy pointers.
STL: shared_ptr has never attempted to support fancy pointers; seems like a paper is needed.
Poll: call construct:6 operator new: 0 don't care: 4
Poll: should we support fancy pointers? Yes: 1 No: 4 don't care: 4
STL: 20.8.2.2.6p2: 'and pv->~T()' is bogus for void
STL: 20.8.2.2.6p4: is this true even if we're going to allocate a bit more?
Alisdair: yes
Alisdair: coming up with new wording

[2016-08, Chicago Monday PM]

Alisdair to provide new wording this week

[2017-07 Toronto]

Resolved by the adoption of P0674R1 in Toronto

Proposed resolution:

This wording is relative to the FDIS.

1. Change the following paragraphs of 20.3.2.2.7 [util.smartptr.shared.create] as indicated:

   >     template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
   >     template<class T, class A, class... Args>
   >       shared_ptr<T> allocate_shared(const A& a, Args&&... args);

   -1- Requires: The expression `::new (pv) T(std::forward<Args>(args)...)`, where `pv` has type `void*` and points to storage suitable to hold an object of type `T`, shall be well formed. `A` shall be an allocator (16.4.4.6 [allocator.requirements]). The copy constructor and destructor of `A` shall not throw exceptions.

   -2- Effects: Equivalent to

   >     return allocate_shared<T>(allocator<T>(), std::forward<Args>(args)...);

   Allocates memory suitable for an object of type `T` and constructs an object in that memory via the placement new expression `::new (pv) T(std::forward<Args>(args)...)`. The template `allocate_shared` uses a copy of `a` to allocate memory. If an exception is thrown, the functions have no effect.

   -?- Remarks: An implementation may meet the effects (and the implied guarantees) without creating the allocator object [Note: That is, user-provided specializations of `std::allocator` may not be instantiated, the expressions `::new (pv) T(std::forward<Args>(args)...)` and `pv->~T()` may be evaluated directly — end note].

   -3- Returns: A `shared_ptr` instance that stores and owns the address of the newly constructed object of type `T`.

   -4- Postconditions: `get() != 0 && use_count() == 1`

   -5- Throws: `bad_alloc`, or an exception thrown from `A::allocate` or from the constructor of `T`.

   -6- Remarks: Implementations are encouraged, but not required, to perform no more than one memory allocation. [Note: This provides efficiency equivalent to an intrusive smart pointer. — end note]

   -7- [Note: These functions will typically allocate more memory than `sizeof(T)` to allow for internal bookkeeping structures such as the reference counts. — end note]

2. Add the following set of new paragraphs immediately following the previous paragraph 7 of 20.3.2.2.7 [util.smartptr.shared.create]:

   >     template<class T, class A, class... Args>
   >       shared_ptr<T> allocate_shared(const A& a, Args&&... args);

   -?- Requires: The expressions `allocator_traits<A>::construct(b, pt, std::forward<Args>(args)...)` and `allocator_traits<A>::destroy(b, pt)` shall be well-formed and well-defined, where `b` has type `A` and is a copy of `a` and where `pt` has type `T*` and points to storage suitable to hold an object of type `T`. `A` shall meet the allocator requirements (16.4.4.6 [allocator.requirements]).

   -?- Effects: Uses an object `a2` of type `allocator_traits<A>::rebind_alloc<unspecified>` that compares equal to `a` to allocate memory suitable for an object of type `T`. Uses a copy `b` of type `A` from `a` to construct an object of type `T` in that memory by calling `allocator_traits<A>::construct(b, pt, std::forward<Args>(args)...)`. If an exception is thrown, the function has no effect.

   -?- Returns: A `shared_ptr` instance that stores and owns the address of the newly constructed object of type `T`. When ownership is given up, the effects are as follows: Uses a copy `b2` of type `A` from `a` to destruct an object of type `T` by calling `allocator_traits<A>::destroy(b2, pt2)` where `pt2` has type `T*` and refers to the newly constructed object. Then uses an object of type `allocator_traits<A>::rebind_alloc<unspecified>` that compares equal to `a` to deallocate the allocated memory.

   -?- Postconditions: `get() != 0 && use_count() == 1`

   -?- Throws: Nothing unless memory allocation or `allocator_traits<A>::construct` throws an exception.

   -?- Remarks: Implementations are encouraged, but not required, to perform no more than one memory allocation. [Note: Such an implementation provides efficiency equivalent to an intrusive smart pointer. — end note]

   -?- [Note: This function will typically allocate more memory than `sizeof(T)` to allow for internal bookkeeping structures such as the reference counts. — end note]