2218. Unclear how containers use allocator_traits::construct()

Section: 23.2.2 [container.requirements.general] Status: C++17 Submitter: Jonathan Wakely Opened: 2012-11-27 Last modified: 2017-07-30

Priority: 3

View other active issues in [container.requirements.general].

View all other issues in [container.requirements.general].

View all issues with C++17 status.

Discussion:

Firstly, 23.2.2 [container.requirements.general]/7 says a container's allocator is used to obtain memory, but it isn't stated explicitly that the same allocator is used to construct and destroy elements, as opposed to a value-initialized allocator of the same type.

Secondly, 23.2.2 [container.requirements.general]/3 says elements "shall be constructed using the allocator_traits<allocator_type>::construct function and destroyed using the allocator_traits<allocator_type>::destroy function" and 23.2.2 [container.requirements.general]/13 defines CopyInsertable etc. in terms of an allocator A which is identical to the container's allocator_type.

The intent of making construct() and destroy() function templates was that containers would be permitted to use allocator_traits<A>::construct() instead of allocator_traits<allocator_type>::construct(), where A is allocator_traits<allocator_type>::rebind_alloc<U> for some other type U. This allows node-based containers to store an allocator of the right type for allocating nodes and to use the same object to construct elements in aligned storage within those nodes, avoiding rebinding and copying the stored allocator every time an element needs to be constructed.

It should be made clear that a possibly-rebound copy of the container's allocator is used for object construction.

[2013-03-15 Issues Teleconference]

Moved to Open.

Jonathan: point 2 in the proposed resolution is definitely needed.

[2014-11-28, Jonathan improves wording]

In the first set of edits to paragraph 3 both pieces inserting "rebind_alloc<U>::" should be replaced by "rebind_traits<U>::"

Otherwise it implies using the allocator's functions directly, but they might not exist and it should be through the rebound traits type.

[2015-05, Lenexa]

STL: You want to permit but not require rebinding?
Wakely: The current wording forces me to use the original allocator, not the rebound one.
STL: Oh, I see. Yeah, we immediately rebind.
Wakely: The edits clarify that we don't use some other allocator. The third diff is because the definitions of EmplaceConstructible/etc. happen with the same types. The diff to the note is because it doesn't require the value of the allocator was the one passed in.
STL: After looking at this, I think I'm comfortable with the edits. The previous Standardese was nonsense so it's pretty easy to improve upon.
Marshall: Any other opinions?
Marshall: Any objections to moving it to Ready? Review? Ready in Kona?
Wakely: My preference would be Ready. We all know this is what we're doing anyways.
Nevin: The intent won't change.
STL: I think this is the right fix.
Hwrd: I third Ready. Even if Jonathan retracts his.
Marshall: Ready!

Proposed resolution:

This wording is relative to N3485.

  1. Edit 23.2.2 [container.requirements.general] paragraph 3:

    For the components affected by this subclause that declare an allocator_type, objects stored in these components shall be constructed using the allocator_traits<allocator_type>::rebind_traits<U>::construct function and destroyed using the allocator_traits<allocator_type>::rebind_traits<U>::destroy function (20.2.9.3 [allocator.traits.members]), where U is either allocator_type::value_type or an internal type used by the container. These functions are called only for the container's element type, not for internal types used by the container. [ Note: This means, for example, that a node-based container might need to construct nodes containing aligned buffers and call construct to place the element into the buffer. — end note ]

  2. Edit 23.2.2 [container.requirements.general] paragraph 7:

    […] A copy of this allocator is used for any memory allocation and element construction performed, by these constructors and by all member functions, during the lifetime of each container object or until the allocator is replaced. […]

  3. Edit 23.2.2 [container.requirements.general] paragraph 13:

    […] Given an allocator type A and given a container type X having an allocator_type identical to A and a value_type identical to T and an allocator_type identical to allocator_traits<A>::rebind_alloc<T> and given an lvalue m of type A, a pointer p of type T*, an expression v of type (possibly const) T, and an rvalue rv of type T, the following terms are defined.

    […]

    [ Note: A container calls allocator_traits<A>::construct(m, p, args) to construct an element at p using args, with m == get_allocator(). The default construct in std::allocator will call ::new((void*)p) T(args), but specialized allocators may choose a different definition. — end note ]