<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name ="description" content="Proposal to directly support types with custom memory allocators in C++">
<meta name ="keywords" content="C++,cplusplus,wg21,EWGI,allocator,scoped allocators,scoped model">
<meta name ="author" content="Alisdair Meredith, Joshua Berne">

<title>Language Support For Scoped Allocators</title>

<style>
    body { counter-reset: section; }

    h2 { counter-reset: subsection; }

    h2::before { counter-increment: section; content: counter(section) " "; }

    h2.uncounted::before { counter-increment: none; content: ""; }

    h3::before { counter-increment: subsection; content:  counter(section) "." counter(subsection) " "; }

    h3.uncounted::before { counter-increment: none; content: ""; }

    ins {background-color:#A0FFA0}
    del {background-color:#FFA0A0}
    p   {text-align:justify}
    li  {text-align:justify}

    ol.index           { counter-reset: item }
    ol.index li        { display: block }
    ol.index li:before { content: counters(item, ".") " "; counter-increment: item }

    ul.index           { counter-reset: depr }
    ul.index li        { display: block }
    ul.index li:before { content: counters(depr, ".") " "; counter-increment: depr }

    blockquote.note {
        background-color:#E0E0E0;
        padding-left: 15px; padding-right: 15px;
        padding-top:   1px; padding-bottom: 1px;
    }

    blockquote.recommend {
        background-color:#ffffb0;
        padding-left: 10px; padding-right: 10px;
        padding-top: 1px; padding-bottom: 1px;
    }

    blockquote.review {
        background-color:#c8ffc8;
        padding-left: 10px; padding-right: 10px;
        padding-top: 1px; padding-bottom: 1px;
    }

    blockquote.review_note {
        background-color:#ffffcc;
        padding-left: 15px; padding-right: 15px;
        padding-top: 1px; padding-bottom: 1px;
    }

    blockquote.draft_wording {
        background-color:#f8f8f8;
        padding-left: 15px; padding-right: 15px;
        padding-top: 1px; padding-bottom: 1px;
    }

    blockquote.old_wording {
        background-color:#f8f0f0;
        padding-left: 15px; padding-right: 15px;
        padding-top: 1px; padding-bottom: 1px;
    }

    blockquote.proposed_wording {
        background-color:#f8f8e8;
        padding-left: 15px; padding-right: 15px;
        padding-top: 1px; padding-bottom: 1px;
    }

    tr:nth-child(even) {background-color: #f2f2f2;}

    table.poll tr {background-color: #fcfcfc; text-align:center;}
    table.poll td {width: 20%;}
</style>
</head>

<body>
<table>
  <tr>
    <td>Doc. no.</td>
    <td>D2685R0</td>
  </tr>
  <tr>
    <td>Date:</td>
    <td>2021-10-14</td>
  </tr>
  <tr>
    <td>Project:</td>
    <td>Programming Language C++</td>
  </tr>
  <tr>
    <td>Audience:</td>
    <td>EWGI</td>
  </tr>
  <tr>
    <td>Reply to:</td>
    <td>
      Alisdair Meredith
      &lt;<a href="mailto:ameredith1@bloomberg.net">ameredith1@bloomberg.net</a>&gt;
    </td>
  </tr>
  <tr>
    <td> </td>
    <td>
      Joshua Berne
      &lt;<a href="mailto:jberne4@bloomberg.net">jberne4@bloomberg.net</a>&gt;
    </td>
  </tr>
</table>

<h1>Language Support For Scoped Allocators</h1>

<h2 class="uncounted">Table of Contents</h2>
<ul>
<li>
  <a href="#rev.hist">Revision History</a>
  <ul>
  <li>
    <a href="#rev.0">Original</a>
  </li>
  </ul>
</li>
</ul>

<ol class="index">
<li>
  <a href="#1.0">Abstract</a>
</li>
<li>
  <a href="#1.1">Introduction</a>
</li>
<li>
  <a href="#2.0">State the Problem</a>
  <ul class="index">
    <li><a href="#2.boilerplate">Scoped Allocator Boilerplate</a></li>
    <li><a href="#2.POCMA">POCCA, POCMA, POCS, and SOCCC!!</a></li>
    <li><a href="#2.aggregate">Scoped Allocators and Aggregates</a></li>
    <li><a href="#2.lambda">Scoped Allocators and Lambda Expressions</a></li>
<!-- <li><a href="#2.generics">Picking Allocator Argument in Generic Constructor</a></li> -->
    <li><a href="#2.move_construct">Allocator-aware Move Construction</a></li>
    <li><a href="#2.swap">Allocator-aware <code>swap</code></a></li>
    <li><a href="#2.noexcept"><code>noexcept</code> and Differing Allocators</a></li>
    <li><a href="#2.lwg">Awkward Standardese</a></li>
  </ul>
</li>
<li>
  <a href="#3.0">Propose a Solution</a>
  <ul class="index">
    <li><a href="#3.scoped">Language Support for Scoped Types</a></li>
    <li><a href="#3.std_pmr">Build on <code>std::pmr</code></a></li>
    <li><a href="#3.allocator_of">Hidden Friend <code>allocator_of</code></a></li>
    <li><a href="#3.move">Move Operations</a></li>
    <li><a href="#3.factory">Implicit Factory Functions</a></li>
  </ul>
</li>
<li>
  <a href="#4.0">Related Proposals</a>
  <ul class="index">
    <li><a href="#4.relocatable">Importance of Trivial Relocatability</a></li>
    <li><a href="#4.noexcept">Runtime <code>noexcept</code></a></li>
<!-- <li><a href="#4.swap"><code>operator swap</code></a></li> -->
  </ul>
</li>
<li>
  <a href="#5.0">Examples</a>
  <ul class="index">
    <li><a href="#5.aggregate">Simple aggregates</a></li>
    <li><a href="#5.vector"><code>std::vector</code></a></li>
    <li><a href="#5.unordered"><code>std::unordered_map</code></a></li>
  </ul>
</li>
<li>
  <a href="#6.0">Open Questions</a>
  <ul class="index">
    <li><a href="#6.union">Support for <code>union</code>s and <code>std::variant</code></a></li>
    <li><a href="#6.optional">Support for <code>std::optional</code></a></li>
    <li><a href="#6.factory">Explicit Factory Functions</a></li>
    <li><a href="#6.expressions"><code>using</code> Beyond Initialization Expressions</a></li>
    <li><a href="#6.move-only">Move-only Scoped Types</a></li>
    <li><a href="#6.scoped">Scoped Types Beyond Allocators</a></li>
    <li><a href="#6.breakage">Controlled Breaking of the Scoped Model</a></li>
  </ul>
</li>
<!--
<li>
  <a href="#7.0">Formal Wording</a>
</li>
-->
<li>
  <a href="#8.0">Acknowledgements</a>
</li>
<li>
  <a href="#9.0">References</a>
</li>
</ol>


<h2 class="uncounted" id="rev.hist">Revision History</h2>

<blockquote>

<h3 class="uncounted" id="rev.0">Original</h3>
<p>
Original version of the paper for the 2022 October (pre-Kona) mailing.
</p>

</blockquote>


<h2 id="1.0">Abstract</h2>

<p>
The C++ Standard Library has tried to support custom memory allocators since
the initial integration of Stepanov's STL into the then-nascent standard
document.  Unfortunately, simple use cases involve much complexity, and not all
constructs &mdash; e.g., aggregates (including c-style arrays) and lambda
expressions &mdash; are amenable to such customization.  After years of
production experience using <em>library-based</em> custom allocators, we are
now ready to begin presenting our proposed minimal but complete set of
<em>language enhancements</em> that would both generalize and greatly simplify
use of custom allocators that follow the <em>scoped allocator model</em>.
</p>

<p>
Our design for language-based allocators is not yet complete; we are, however
actively seeking early feedback on outstanding questions that must be resolved
before bringing this work back as a full proposal.
</p>

<p>
This paper aims to help us gauge interest, understand the full scope of
potential users, and find collaborators.
</p>

<h2 id="1.1">Introduction</h2>

<p>
Today's standard library has nearly full support for writing types that make
effective use of (scoped) <em>polymorphic allocators</em>.  The library
components provided in <code>std::pmr</code> (C++17) bring to fruition the
results of decades of effort by many parties to realize the local
(<em>runtime</em> polymorphic) allocator model, pioneered by John Lakos at Bear
Stearns (c. 1997-2001) and refined and used heavily by the BDE team at
Bloomberg (c. 2002-).  With <code>std::pmr</code> the decades of benefit that
were enjoyed locally are now readily accessible to all users of standard C++.  
</p>

<p>
The use of <code>pmr</code> scoped allocators, however, still comes with
significant complexities, development and maintenance costs, and limitations in
interoperability with other C++ language constructs.  Fortunately such
complexities, costs, and limitations are not (or need not be) endemic to custom
allocators.  As this paper aims to demonstrate, the "everyone's is a winner!"
solution to the (optional) use of custom allocators lies with a
<em>language-based</em> solution.
</p>

<p>
The difficulties inherent in using scoped allocators today arise from two
primary sources, (1) excessive boilerplate source code and (2) lack of
interoperability with several other features in the language.  First, any
object that contains an <em>allocator-aware (AA)</em> base class or member will
need to provide significant boilerplate constructors and operators, which are
both costly and error prone to write by hand. In particular, the <em>rule of
zero (RoZ)</em> cannot be applied to such types.  Second, objects for which
users choose not to (e.g., aggregates) or cannot (e.g., lambdas) write their
own constructors <em>cannot</em> be made allocator-aware, removing the ability
to leverage many modern C++ features when writing allocator-aware types.  On
top of those two concerns there are many challenging edge cases in type
implementation that are made more verbose and error-prone for allocator-aware
types, just to handle the varied ways in which allocators can be passed and
must be managed during an object's lifetime.  We will dig into these issues in
much more detail as part of delineating the specific problems we hope to solve.
</p>

<p>
The central facet of our proposed solution is to make an addition to the syntax
of initialization, leveraging the <code>using</code> keyword, that allows for
the notion of a <em>side channel</em> to pass the allocator through any
construction of a type that contains such a <em>scoped property</em> (which we
generalize below).  This initialization back channel transitively and
automatically  passes a consistent value to initialize the value of the scoped
property to the initialization of all base classes and members, enabling the
maintenance of the fundamental invariants of the scoped model with no need for
additional code.
</p>

<p>
As part of this initial presentation, we'll also explore many of the corners of
the language that will need to be addressed to complete support of this kind of
scoped property, and importantly we will show how the availability of these
features would drastically simplify the design, specification, and
implementation of various common types that currently support generic
allocators.
</p>

<p>
Finally, in addition to having a quarter century's experience working with
local allocators in production, the folks at Bloomberg &mdash; this author in
particular &mdash; have been researching this topic for better than five years
now.  Though we have long since settled on a general syntax and come to many
conclusions as to where and how this feature would best integrate into a more
cohesive version of the C++ language, we still have some open issues, and our
lowest-level design for language-based allocators is still a work in progress.
Hence, we have chosen below to pose a number of open questions in lieu of
providing a complete proposal that we ourselves know is not yet fully baked.
We hope to evolve this proposal into something more general that tackles all
known potential use cases cleanly and intuitively.
</p>

<p>
Our final proposal will facilitate making use of objects having <em>scoped
allocators</em> without needing to write any boilerplate to support them in any
layers above those that do raw memory allocation directly &mdash; most of which
will be provided by the standard library.  Once complete, the <em>rule-of-zero
(RoZ)</em> will at last be applicable to <em>allocator-enabled (AE)</em> types,
many language-generated objects will naturally support allocators if and as
needed, and writing allocator-aware software will become seamless, much easier
to write, and drastically less error-prone.  Moreover, we hope to generalize
the support needed for such <em>scoped properties</em> to enable new designs
beyond allocators to other domains, such as executors or loggers, where a side
channel with well-defined automatic scoping could be beneficial.
</p>


<h2 id="2.0">State the Problem</h2>
<p>
The C++ Standard Library has long supported custom allocators, but in practice
their use has been largely limited to stateless allocators that are bound at
compile-time, due to a variety of issues that arise once allocator state is
embraced.  C++11 improved support for stateful allocators through the use of
<code>std::allocator_traits</code>, which first tackled the tricky issue of
allocator propagation, which is particular to stateful allocators.
</p>

<p>
C++17 further improved support for making use of a single, general-purpose
stateful scoped allocator with the addition of
<code>std::pmr::polymorphic_allocator</code>.  By specifying specific choices
for the complex set of behaviors available to a fully-generic allocator
template argument it became possible to write significant code that separated
the choice of allocation strategy from the implementation of a type and made it
something that could be chosen optimally at runtime.  In particular, the
availability of a common vocabulary for a polymorphic allocator type allows for
choosing distinct allocation algorithms for distinct tasks, often for the same
type within the same program.
</p>

<p>
One of the key principles underlying the use of
<code>std::pmr::polymorphic_allocator</code> is the <em>scoped object
model</em>, which it implements.  The idea of a scoped object is that some key
aspect of a data structure must be accessible at any point within the scope of
that data structure.  Hence, any base object, data member, or dynamically
allocated member of the data structure that has behavior depending on that
aspect will store a reference to an implementation of such.  All parts of the
data structure must refer to that same implementation, for consistent behavior.
Once an object is initialized with such a reference, it cannot be rebound, or
else we lose the guarantee that all parts of the data structure have the same
behavior.  This leads to the notion that scoped objects do not propagate their
scoped aspect to another object on copy, assignment, swap, etc., which is
counter-intuitive to the traditional C++ object model where, by default, all
members would transfer on a copy, move, or swap.  Much of this proposal deals
with making the use of scoped objects more intuitive in the language.
</p>


<h3 id="2.boilerplate">Scoped Allocator Boilerplate</h3>
<p>
There is a lot of repetitive code that must be written to make a type properly
allocator-aware, supporting the stateful scoped allocator model.  Every such
class must have a type alias named <code>allocator_type</code>, which is
detected by the <code>uses_allocator</code> type trait, and an
allocator-accepting overload of every constructor.  The latter is often
accomplished using default arguments, but there may need to be many new
constructor overloads if there are already constructors relying on further
default arguments.  For example this was missed in the initial specification of
unordered containers, and took a couple of subsequent standards to get right.
See <a href="#5.unordered">below</a> for an exploration of the excessive
constructors in the unordered containers.
</p>

<p>
The additional overloads for trailing default arguments can be mitigated by
moving the allocator the to the front of the parameter list, preceded by
<code>std::allocator_arg_t</code>, and this is the preferred choice for new
types.  However, this now means that uses-allocator construction must be aware
of both conventions, and use some form of reflection to determine which rule to
follow for a given initialization.  That reflection is achieved today with some
primitive, but otherwise needlessly expensive, template metaprogramming.  For
example, take a look at the implementations of
<code>make_obj_using_allocator</code> and 
<code>uninitialized_construct_using_allocator</code> in your favorite standard
library. 
</p>

<p>
The addition of the allocator parameter means that a type cannot simply default
its default, move, and copy constructors, but must define the
allocator-accepting overloads, even when the class does not directly store the
allocator to allocate memory itself, and the constructors merely pass the
allocator along to relevant bases and members.
</p>

<p>
Similarly, if the class does directly store a copy of the allocator itself, the
assignment operators must also be user provided, to handle correct behavior of
not assigning to the allocator member.  The same issue arises for defaulted
comparison operators which must not consider the allocator as a constituent
part of the object's value.
</p>


<h3 id="2.POCMA">POCCA, POCMA, POCS, and SOCCC!</h3>
<p>
Allocators expose a number of different traits (through
<code>allocator_traits</code>) which control how they should be modified (or
not) upon various operations being applied to the objects that use them.  These
include three boolean traits -
<code>propagate_on_container_copy_assignment</code> (POCCA),
<code>propagate_on_container_move_assignment</code> (POCMA), and
<code>propagate_on_container_swap</code> (POCS), as well as a selection
function <code>select_on_container_copy_construction()</code> (SOCCC).
</p>

<p>
These allocator propagation traits are generally the largest source of
confusion when learning the scoped allocator model.  The idea that an allocator
does not propagate through copy and assignment is not the default, expected,
behavior of a C++ type, but is essential to the correct behavior of scoped
types.  Part of the reason for this is to make tractable any reasoning about
the lifetime of memory resources; the allocator for each member is always the
allocator of the complete object, so we need reason only about the lifetime of
that container relative to the memory resource, and not worry about elements
being constructed with different transient memory resources.
</p>

<p>
The second reason for this guarantee is the ability to rely on every part of
the data structure having the same allocator.  This is essential for a nothrow
move, and a proper <code>swap</code> function, which satisfy preconditions for
many standard algorithms.
</p>  <!-- define "proper" -->

<p>
Life gets much harder when we must consider the possibility of inconsistent
propagation traits though.  For example, what are the implications for a type
that propagates on move construction, but not on <code>swap</code>?  Or vice
versa?  Generic wrapper types must be careful to always define a move
constructor in case they wrap a type with an allocator that propagates on move,
but not on copy (or vice versa).
</p>

<p>
Testing types that support generic allocators becomes a much larger task when
allowing for all possible combinations of traits, including the
<code>select_on_container_copy_construction</code> function, rather than being
able to rely on either all traits propagate, or none do.
</p>


<h3 id="2.aggregate">Scoped Allocators and Aggregates</h3>
<p>
As aggregates are not allowed to define constructors, there is no way to force
consistency of allocators, nor a way for containers and other types to pass an
allocator at initialization.  Consequently, any attempt to force
<code>uses_allocator_v&lt;Aggregate, Allocator&gt;</code> to be
<code>true</code> will produce a type that behaves badly in a container.
</p>

<p>
A determined user might still be able to force correct behavior by carefully
enforcing consistent allocators at initialization, and using member functions
that rely specifically on moving to insert into a container.  Careful usage
will behave correctly, until the first single accidental insertion by copy
inserts an element using the system default memory resource, breaking the
single allocator for all elements guarantee.
</p>

<p>
For example:
</p>

<blockquote class="draft_wording"><pre>
struct Aggregate {
   std::pmr::string data1;
   std::pmr::string data2;
   std::pmr::string data3;
};

std::pmr::polymorphic_allocator a1;
Aggregate ag  = {{"Hello", a1}, {"World", a1}, {"!", a1}};

std::pmr::vector&lt;Aggregate&gt; va(a1);
va.emplace_back(std::move(ag));   <i>// Correct allocator retained by moves</i>
va.emplace_back(ag);              <i>// Error, copied lvalue has wrong allocator</i>
va.resize(5);                     <i>// Error, new values have wrong allocator</i>
va.resize(1);                     <i>// OK, remove all objects with bad allocators</i>
</pre></blockquote>

<p>
That this example works at all is due to aggregates move/copy being simple memberwise move and
copy, so the individual members enforce consistency if initialized correctly.
There is a another <a href="#5.aggregate">worked example</a> further down the
paper that looks into mitigations for this, and how our proposal leads to a
much simpler user experience.
</p>

<p>
Note that we are relying on <code>Aggregate</code> having a <code>noexcept</code>
move constructor.  What happens if we try to use <code>std::pmr::list</code>?
</p>

<blockquote class="draft_wording"><pre>
std::pmr::polymorphic_allocator a2;
Aggregate agl  = {{"Hello", a2}, {"World", a2}, {"!", a2}};

std::pmr::list&lt;Aggregate&gt; lsa1(a2);
lsa1.emplace_back(std::move(ag));   <i>// Correct allocator retained by moves</i>
std::pmr::list&lt;Aggregate&gt; lsa2 = std::move(lsa1);  // QoI: may throw


std::pmr::vector&lt;std::pmr::list&lt;Aggregate&gt;&gt; vla(a2);
vla.emplace_back(std::move(lsa1));  <i>// Correct allocator retained by move</i>
vla.reserve(5);                     <i>// QoI on </i>list<i> whether correct allocator retained</i>
</pre></blockquote>

<p>
The issue here is that it is QoI whether <code>std::list</code> has a
nonthrowing move constructor, to allow for implementations that need to
allocate a sentinel node after moving.  Depending on whether the implementation
of <code>list</code> has a nonthrowing move constructor, <code>vector</code>
will either move or copy the existing elements into the new buffer allocated by
<code>reserve</code>.
</p>


<h3 id="2.lambda">Scoped Allocators and Lambda Expressions</h3>
<p>
Lambda expressions carry the same problem as aggregates, that users cannot
specify an allocator for the capture lists, producing a type that is not
allocator-aware, and similarly cannot specialize the <code>uses_allocator</code>
trait.
</p>


<!--
<h3 id="2.generics">Picking Allocator Argument in Generic Constructor</h3>
<p>
REWRITE NEEDED HERE
</p>
-->

<h3 id="2.move_construct">Allocator-aware Move Construction</h3>
<p>
One principle underlying the scoped allocator model is that all initialization,
when not explicitly supplied an allocator, uses the system supplied default
memory resource.  However, this breaks down for the move constructor, that
clearly cannot achieve the expected efficiency for a move if the new object
does not have the same allocator as the moved-from object.  Hence, there is
a specific exception to the rule so that the move constructor should take its
allocator from the moving argument.
</p>

<p>
Now let us consider the following simple example:
</p>

<blockquote class="draft_wording"><pre>
struct MyType {

   MyType(MyType const &amp; other, std::pmr::polymorphic_allocator&lt;&gt; a = {});
   MyType(MyType      &amp;&amp; other) = default;
   MyType(MyType      &amp;&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a);

private:
   std::pmr::vector&lt;int&gt;  d_data;
   std::pmr::vector&lt;int*&gt; d_index;
};
</pre></blockquote>

<p>
This class maintains an invariant that the pointers in <code>d_index</code>
always refer to elements in the vector <code>d_data</code>.  This invariant is
preserved in the definition of the copy constructor.  It is preserved by the
defaulted move constructor, as both vectors move and that preserves the
invariant.  The question arises as to what is the preferred semantic of the
move-with-allocator constructor?  A naive implementation might perform a
memberwise move-with-allocator construction for each data member, however that
would break the invariant.  A better implementation would be to use a
delegating constructor to simply forward to the copy constructor, that enforces
the invariant.  However, this implementation loses the optimized move when the
supplied allocator argument matches that of the <code>other</code> argument.
The preferred solution is therefore to test if the allocators are equal, and
then delegate to either the move constructor, or the copy-with-allocator
constructor.  This is our recommended default best practice, and one possible
implementation is part of the <a href="#5.a.move">worked example</a> below.
</p>

<p>
Issues surrounding move-only scoped allocator types remain an
<a href="#6.move-only">open question</a>
</p>


<h3 id="2.swap">Allocator-aware <code>swap</code></h3>
<p>
The generic <code>swap</code> function that forms the basis of the swappable
requirements and concepts has a wide contract that permits exceptions.  There
is a lot of code that expects "best practice" where the <code>swap</code>
function cannot throw, and has constant complexity.  While this is not
guaranteed for the general case, the standard provides this guarantee where it
is able, such as for the standard containers.
</p>

<p>
When two containers have different allocators that do not propagate, it is not
possible to implement a non-throwing, constant complexity, <code>swap</code>
function.  These performance guarantees are available only at run time, when
the allocators compare equal.
</p>

<p>
Hence, implementations of scoped types must choose between narrowing the wide
contract on <code>swap</code> with a precondition that allocators compare
equal (noting the lack of a standard requirement to provide a
<code>get_allocator</code> function to check, although it is frequently added
as best practice), or allowing the function to throw and make copies knowing
that copies are often not constant time constructs.
</p>

<p>
The standard has gone with narrowing the contract, although this is thought by
many to be a mistake, and there remains an open issue on this question,
<a href="https://cplusplus.github.io/LWG/issue2153">LWG #2153</a>.  Note that
while it is reasonable to later specify specific behavior to replace UB, as any
assumptions on such code are already broken, it is not simple to declare
existing behavior undefined.
</p>


<h3 id="2.noexcept"><code>noexcept</code> and Differing Allocators</h3>
<p>
There is a general concern for functions that adopt or exchange resources that,
in order to avoid allocation, there is a precondition that the objects have the
same allocator at runtime; otherwise allocation may occur, and so the operation
may throw.  Hence, many operations that we would like to indicate do not throw
cannot lean on C++ language features such as <code>noexcept</code> that enforce
the constraint at compile time.  For example, the move-assignment operator will
typically be <code>noexcept(false)</code>, and the standard library containers
put a precondition on non-member <code>swap</code>.
</p>
<p>
This concern is relatively minor for non-template code, as the specific types
with their known contracts allow testing whether the allocators are the same
before selecting an implementation algorithm.  This concern is much harder to
work around in generic code though, where simply knowing whether move
assignment or swapping may throw is the trigger for choosing different
implementation algorithms.
</p>


<h3 id="2.lwg">Awkward Standardese</h3>
<p>
Quoting Jonathan Wakely (with permission) from a reflector discussion on
<a href="https://cplusplus.github.io/LWG/issue3758">LWG issue 3758</a>:
</p>
<blockquote>
Of course this all ignores the problem that the container has no real way to
know whether move insertion is potentially throwing, because it doesn't know
which constructor move insertion will use, that's decided by the allocator. So
the container checks <code>is_nothrow_move_constructible</code> to decide
whether to move insert or copy insert, but the allocator might not use the same
constructor that <code>is_nothrow_move_constructible</code> considers. Yay.
</blockquote>

<p>
The issue here is that container elements are initialized by
<code>allocator_traits::construct</code> that, by default, is implemented with
<i>uses-allocator construction</i> where the actual constructor chosen is
determined by a formula looking to see if an allocator is supported.  If an
allocator supporting constructor is chosen, it is expected to have the same
noexcept guarantees as the same constructor that does not accept an allocator,
but there are no guarantees in the standard that this must be the case.  Worse,
the optional <code>construct</code> member function of an allocator is a
deliberate extension point to allow the allocator to use other initialization
forms that might not call the <code>noexcept</code> constructor that the
container requirements are specified in terms of.  In practice, we write code
to support the intent, but there are no such guarantees present in the standard
itself.
</p>


<h2 id="3.0">Propose a Solution</h2>
<p>
The following proposes a partial solution, with the details we have worked
through to a reasonable level of confidence.  It is expected some minor details
may change as we address the <a href="#6.0">Open Questions</a> deeper in this
paper, but these are the foundations that a complete solution will build on.
</p>

<p>
For simplicity of our current proposal, we are focusing exclusively on scoped
allocators of a single type, but see the discussion of
<a href="#6.scoped">Scoped Types Beyond Allocators</a> towards end the of this
document.
</p>

<p>
For the purpose of illustration, all examples of new library code will be
written in namespace <code>std2</code>.  We are not proposing any library
extensions at this point, so this should not be seen as proposing such.
However, it is a conveniently short namespace for our examples, and distinct
from namespace <code>std</code> itself.  Where code uses <code>std</code>
rather than <code>std2</code> it is intentional, e.g., to illustrate that
<code>std::tuple</code> needs no changes to support the new facilities.
</p>
<p>
Most of our examples in <code>std2</code> show types that would be equivalent
to the corresponding type in <code>std::pmr</code>, for familiar examples with
significantly less boilerplate standardese being needed.
</p>


<h3 id="3.scoped">Language Support for Scoped Types</h3>
<p>
The essential problem that needs resolving is the complexity inherent in the
scoped model, and how it has different expectations to the defaults inherent
in the C++ language.  This leads to requiring support to pass allocators to
every constructor/initializer, and simply requiring this property violates the
rule of zero, putting aggregates such as arrays beyond our ability to support.
Hence, the place to start is with language support to make the scoped model as
well supported as today's defaults, re-enabling the rule of zero.
</p>

<h4 id="3.scoped.using">Pass via <code>using</code></h4>
<p>
The essential idea is that a scoped property, in this case an allocator, must
be supported in every initializer.  That means that every initialization syntax
must allow an optionally supplied allocator, and have a good default behavior
for the (common) case that no allocator is supplied.
</p>
<p>
To avoid redundancy in constructors, and to reach types where there is no
ability to write constructors, we need another mechanism to pass the scoped
values to object initialization.  We propose to do this with a new
<i>using-expression</i> on variable initialization.  We pick on the
<code>using</code> keyword as the best grammatical English token that is
already reserved, and sufficiently underloaded within initialization to serve
our purposes.  A simple example of this syntax might be:
</p>

<blockquote class="proposed_wording"><pre>
int main() {
   std2::test_resource tr;
   std2::vector vi <ins>using tr</ins> = {1, 2, 3};
}
</pre></blockquote>

<p>
Our initial idea was that <code>using</code> feels more intuitive at the end of
any expression (or subexpression), as that feels like it would expand into many
more use cases.  However, that turned out to be why we opted against that
syntax, at least for the initial proposal, as it leads to much more complexity,
parsing ambiguities that would need to be resolved, and distracts from our
primary focus, at least for now.  See the
<a href="#6.expressions">Open Questions</a> for further discussion.
</p>

<blockquote class="old_wording"><pre>
int main() {
   std2::test_resource tr;
   std2::vector vi = {1, 2, 3} <del>using tr</del>;  // Error! `using` does not go here
}
</pre></blockquote>

<h4 id="3.scoped.implicit">Implicit Propagation</h4>
<p>
The essential feature of the scoped allocator model is that all objects that
participate in the same data structure, meaning base classes, non-static data
members, and dynamically allocated data, must use the same allocator.  There
is a lot of boiler-plate code to get correct in constructors to ensure that
this property holds, so we propose a feature where <i>allocator-enabled</i>
types are implicitly supplied their allocator in the base and member
initializers from their derived/containing class, and that there is no other
syntax to accidentally override this.
</p>
<p>
An allocator-enabled class is defined recursively, as a class that comprises
any allocator-enabled base classes or non-static data members.  At the root of
this hierarchy will be a new fundamental type, tentatively named
<code>_Resource</code> prior to a formal naming discussion, that will have
further special properties detailed below.
</p>

<p>
Here we show how regular language arrays, which have no constructors, can now
support elements that use allocators via the implicit propagation property of
scoped element types.
</p>
<blockquote class="proposed_wording"><pre>
int main() {
   std2::test_resource tr;
   std2::string s2[] <ins>using tr</ins> = { "Hello", "world" };
}
</pre></blockquote>

<p>
Similarly, we should expect the standard types to implicitly support our scoped
behavior, where they directly store an object of template parameter type, as
all the constructors implicitly pick up a scoped allocator argument.
</p>
<blockquote class="proposed_wording"><pre>
int main() {
   using namespace std2::string_literals;
   std2::test_resource tr;

   std::pair  p2 <ins>using tr</ins> = { "Hello"s, "world"s };
   std::tuple t4 <ins>using tr</ins> = { "Bonjour"s, "tout"s, "le"s, "mond"s };
}
</pre></blockquote>

<h3 id="3.scoped.default">Implicit and Defaulted Operators</h3>
<p>
Objects that support the scoped model share a couple of features.  The scoped
member cannot be changed, as it does not propagate.  This would make all
assignment operators ill-formed (and implicitly deleted) without a
user-provided definition that ignored the scoped object.  Similarly, the scoped
object is typically <em>not</em> a salient data member for comparison, and
comparing the scoped values would produce false-negative results.  For example,
scoped allocators are never salient.
</p>
<p>
The ideal default behavior for implementation-provided assignment and
comparison operators for types that comprise scoped objects (such as
allocators) is to simply ignore scoped members, leaving them unchanged.
</p>

<h4 id="3.scoped.alloc_traits">Eliminate dependency on <code>std::allocator_traits</code></h4>
<p>
The standard allocator-aware containers have a dependency on
<code>std::allocator_traits</code> for any use of an allocator.  This is what
supports such versatility as allocators returning <i>fancy</i> pointers,
having a variety of different allocator propagation traits (POCMA etc.), and
the ability to customize allocation of elements, e.g., using the container's
allocator for allocators supporting the scoped allocator model.
</p>
<p>
The <code>std::allocator_traits</code> model also comes with assumptions that
allocators are passed as regular function arguments, which would no longer hold
under our new language-supported model.  Note that <code>constexpr</code>
allocation is also built on top of <code>std::allocator_traits</code>, which
may be a problem we must tackle later - see open questions.
</p>
<p>
This proposal deliberately removes much of the flexibility supported by
<code>allocator_traits</code>, baking in such assumptions as never propagating
allocators, and <code>allocate</code> returning a plain pointer.  This will
allow us to simplify our assumptions, providing the appropriate default
behavior in each case, so indirection through <code>allocator_traits</code>
should be unnecessary.  While it is certainly possible to build an allocator on
top of these new facilities that would plug into <code>allocator_traits</code>
the experience might be less than ideal, as the <code>allocator_traits</code>
interface is designed for types that take actual allocator arguments, rather
than following the general purpose model we propose where allocators must
necessarily be passed independently from the initializer arguments.
</p>


<h3 id="3.std_pmr">Build on <code>std::pmr</code></h3>
<p>
The intent of this paper is to propose flexible support for allocators, while
also taking the allocator parameter out of class templates.  This is the basis
of the <code>std::pmr</code> polymorphic memory resource facility added to
C++17, so our proposal will simply adopt this protocol rather than re-invent
that particular wheel.  We refer to the material of that time for reference to
why this is an important and good fit for providing allocators at runtime.
</p>


<h3 id="3.allocator_of">Hidden Friend <code>allocator_of</code></h3>
<p>
An important part of the polymorphic allocator protocol is to determine the
allocator used by any given allocator-enabled object.  We propose adding a
"hidden friend" function, <code>allocator_of</code>, that can answer this
question.  Furthermore, <code>allocator_of</code> will be implicitly defined
for any allocator-enabled type to return <code>allocator_of</code> one of the
bases or members that granted that status to the class.  There will be some
extra core wording to transform <code>allocator_of</code> an array to the
<code>allocator_of</code> one of its elements.
</p>


<h3 id="3.move">Move Operations</h3>
<p>
One of the tricky problems for allocator enabled types is the notion of move
operations.  One fundamental property is that the allocator never propagates,
and we can easily build support for that into our proposal.  However, the
<code><i>pmr</i></code> model comes with the additional constraint that move
operations are potentially throwing if the memory resources are not the same.
The simplest consequence is that the move-assignment operator will always be
<code>noexcept(false)</code>.  The deeper question is whether we can get move
construction right?
</p>
<p>
The basic issue is that if we do not supply an allocator via <code>using</code>
to initialize an object, then the system default resource will be used.  This
turns out to be the intended behavior for the copy constructor, but not for the
move constructor.  To properly effect a move, we need the newly constructed
object to have the same allocator as the moving object.  Hence, we propose a
special rule when initializing from an rvalue.
</p>
  <blockquote class="note">
    Should we restrict to rvalue of the same type, or any rvalue?
  </blockquote>
<p>
The move constructor will always receive the allocator of the rvalue when there
is no supplied <code>using</code> argument.  If a <code>using</code> allocator
is supplied, then, at the call point, the compiler will generate code to
compare the supplied allocator with that of the rvalue.  If the allocators are
the same, then the move constructor is called.  If the allocators are different
then the <em>copy</em> constructor is called with the supplied allocator; if
the copy constructors is not available, then the program is ill-formed.
</p>
<p>
We may wish to allow customization of this behavior by overloading the move
constructor with original and allocator-enabled forms, but that is left as an
open question for now, as we do not (yet) propose a syntax to pass allocators
to arbitrary functions for overloading.
</p>
<p>
Also considered was making it undefined behavior or throwing a
<code>bad_allocator</code> exception if the copy constructors is not available.
We would like to see stronger motivation before injecting more UB or another
exception from the language itself.  This remains an
<a href="#6.move-only">open question</a>.
</p>


<h3 id="3.factory">Implicit Factory Functions</h3>
<p>
A <i>factory function</i> is any function that returns an object by value.
For the purposes of this proposal, an <i>implicit factory function</i> is any
factory function that returns an allocator-aware type, and is shorthand for
&quot;implicitly allocator-enabled factory function&quot;.
</p>

<h4 id="3.factory.using">Passed an Allocator</h4>
<p>
When a factory function returns an allocator-enabled object by value, clearly
the caller would like to be able to supply an allocator that the returned
value would use.  In such cases, the factory function will implicitly accept
an allocator via the <code>using</code> mechanism.
</p>
<p>
Note that for the initial proposal, the only place a <code>using</code> clause
is valid is on variable declarations, so at this point the only way to supply
the allocator to a factory function is to declare a variable, and supply the
allocator to the declared variable.  That same allocator will be supplied to
the implicit <code>using</code> clause for the factory function.
</p>

<h4 id="3.factory.return">Implicit <code>using</code> on Return Expressions</h4>
<p>
Every <code>return</code> expression in an implicit factory function comes with
an implicit <code>using</code> of the allocator supplied to the function call.
This <code>using</code> will inhibit RVO unless the compiler can prove that the
returned object was constructed with the same allocator.  Note that this will
trivially be the case where RVO has been mandated since C++17/20.
</p>

<h4 id="3.factory.implict_default">Potential Return Variables</h4>
<p>
A potential return variable is any value that, once constructed, will be
subject to a return expression.  A function may have multiple potential return
variables, such as when an <code>if</code>/<code>else</code> clause contains
two different object and return paths.  More detailed examples may be found in
the separate proposal:
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2025r2">P2025R2</a>
Guaranteed copy elision for return variables, although a simpler formulation
may suffice for this paper, and RVO may remain optional rather than guaranteed.
</p>
<p>
In general, potential return variables, as well as any variable currently
subject to potential NRVO, should not include the case of a variable with an
explicit <code>using</code> clause, as that using clause may conflict with the
allocator supplied to the factory function.
</p>
<p>
Where the compiler can identify a declared variable as a <i>potential return
variable</i>, initialization of that variable will have an implicit
<code>using</code> clause to use the allocator supplied to the factory
function.  This will result in either constructing the variable in-place with
the requested allocator or move-constructing it with an already matching
allocator upon <code>return</code>.
</p>


<h2 id="4.0">Related Proposals</h2>

<h3 id="4.relocatable">Importance of Trivial Relocatability</h3>
<p>
One of the big optimizations of C++11 was the move optimization when growing a
<code>vector</code>.  If the element type has a non-throwing move constructor,
then we can move all of the elements from the current <code>vector</code>
buffer into the new, larger, buffer just allocated for that
<code>vector</code>, potentially saving many re-allocations by each element.
However, if we cannot prove that such moves are non-throwing (such as by
querying the <code>noexcept</code> specification on the move constructor) then,
in order to preserve the strong exception safety guarantees provided by this
container, we must fall back on the whole <code>copy</code>/<code>swap</code>
idiom, doing the work in a side buffer until we are ready to commit, and making
a full copy of each element in the process.
</p>
<p>
While it is relatively easy to provide a non-throwing (and
<code>noexcept</code>) move constructor for allocator-enabled types,
<code>vector</code> will actually call the move-with-allocator constructor,
that will always be <code>noexcept(false)</code>.  The type system cannot see
the guarantee that the scoped model allocator being supplied at runtime will
guarantee, <em>at runtime</em> that no exceptions will propagate.
</p>
<p>
Many such types would be <i>trivially relocatable</i> though, and optimizations
built around this property may become more important.
</p>


<h3 id="4.noexcept">Runtime <code>noexcept</code></h3>
<p>
As noted above, in many cases allocator-enabled types can report at runtime,
rather than compile-time, whether there is a risk of propagating an exception.
If we could query for such a property, then library code could optimize with
a regular <code>if</code> statement at runtime, rather than forcing that same
optimization choice be exclusively at compile time.
</p>

<p>
A facility to expose such a runtime query from a type would complete the
picture, but as we have noted it is of lower importance in the presence of
trivial relocatability.
</p>


<!--
<h3 id="4.swap"><code>operator swap</code></h3>
<p>
</p>
-->

<h2 id="5.0">Examples</h2>

<h3 id="5.aggregate">Simple aggregates</h3>
<p>
Two of the initial motivators that lead to this paper was the awkwardness of
aggregates with allocators, and the amount of boilerplate associated with
writing a proper allocator-aware class.  This example illustrates both, as the
amount of boilerplate when emulating a simple aggregate emphasizes the work
involved, that is often just careful attention to details that must otherwise
be addressed anyway for a non-aggregate class.
</p>

<h4 id="5.a.basic">A basic aggregate</h4>
<p>
Let us consider a simple aggregate using a scoped allocator type in C++23.  For
simplicity we will use <code>std::pmr::string</code> as our scoped allocator
aware data member.  To complete the example, we also provide the key comparison
operators, which we can simply default.  To simplify code comparison, all the
subsequent example types will have an identifier of exactly the same length.
</p>

<blockquote class="draft_wording"><pre>
struct BasicAggregate {
   std::pmr::string data1;
   std::pmr::string data2;
   std::pmr::string data3;

   friend auto operator ==(BasicAggregate const &amp;, BasicAggregate const &amp;) -&gt; bool = default;
   friend auto operator&lt;=&gt;(BasicAggregate const &amp;, BasicAggregate const &amp;) -&gt; std::strong_ordering = default;
};
</pre></blockquote>

<p>
<code>BasicAggregate</code> does not work well as the type stored in a
<code>std::pmr::vector</code> as it does not advertise that it uses
<code>pmr</code> allocators &mdash; which is a good thing, as it does not!
There is no easy way to supply an allocator to the members, although a
determined user will find a way:
</p>

<blockquote class="draft_wording"><pre>
std::pmr::polymorphic_allocator a1;
BasicAggregate bd;
BasicAggregate bd1 = {"Hello"};
BasicAggregate bd2 = {"Hello", "World"};
BasicAggregate bd3 = {"Hello", "World", "!"};

BasicAggregate b   = {{{}, a1}, {{}, a1}, {{}, a1}};
BasicAggregate b1  = {{"Hello", a1}, {{}, a1}, {{}, a1}};
BasicAggregate b2  = {{"Hello", a1}, {"World", a1}, {{}, a1}};
BasicAggregate b3  = {{"Hello", a1}, {"World", a1}, {"!", a1}};
</pre></blockquote>

<p>
Observe that in order to enforce a consistent allocator, all aggregate members
must be aggregate initialized, with the same allocator passed to initialization
in each case.  Once initialized this way though, the correct allocator will be
used for copy and move construction, as those are performed memberwise for an
aggregate, and <code>std::pmr::polymorphic_allocator</code> has the correct
behavior.
</p>

<h4 id="5.a.array"><code>std::array</code> as an aggregate</h4>
<p>
Some of you may have noticed that <code>std::array</code> also provides a
homogeneous aggregate, and also supplied the comparison operator, so how would
this code look using <code>std::array</code>?
</p>

<blockquote class="draft_wording"><pre>
using ArrayAggregate = std::array&lt;std::pmr::string, 3&gt;;
ArrayAggregate aps   = {<ins>{</ins>{{}, a1}, {{}, a1}, {{}, a1}<ins>}</ins>};
ArrayAggregate aps1  = {<ins>{</ins>{"Hello", a1}, {{}, a1}, {{}, a1}<ins>}</ins>};
ArrayAggregate aps2  = {<ins>{</ins>{"Hello", a1}, {"World", a1}, {{}, a1}<ins>}</ins>};
ArrayAggregate aps3  = {<ins>{</ins>{"Hello", a1}, {"World", a1}, {"!", a1}<ins>}</ins>};

<i>// Accept the default allocator</i>

ArrayAggregate apsd;
ArrayAggregate apsd1 = {"Hello"};
ArrayAggregate apsd2 = {"Hello", "World"};
ArrayAggregate apsd3 = {"Hello", "World", "!"};
</pre></blockquote>

<p>
Here we observe that <em>yet another</em> pair of braces is needed to surround
the object initialization, so that the aggregate nested within
<code>std::array</code> takes the whole supplied initialization, rather than
trying to initialize the internal aggregate with just the first braced
initializer.  The simplicity when using just the defaults is repeated for
comparison.
</p>

<h4 id="5.a.emul">Emulating an aggregate</h4>
<p>
No matter the effort we run to to force a consistent set of scoped allocators
across all of our elements though, aggregate types can never serve as proper
scoped allocator types element types for standard <code>pmr></code> containers
as:
</p>
<ul>
  <li>
  They do not have an <code>allocator_type</code> member, nor do they specialize
  <code>std::uses_allocator</code> to inform the container that they desire an
  allocator.
  </li>
  <li>
  They do not have an initialization that accepts a single allocator that is
  supplied to all (relevant) members.
  </li>
</ul>
<p>
Suppose we wished to write a properly allocator-aware class that is a drop-in
substitute for our <code>BasicAggregate</code> in almost every way, but that
correctly supports <code>pmr</code> allocators?  That might looks something
like the following.  Note that modern best practice suggests we use
<code>std::pmr::polymorphic_allocator&lt;&gt;</code> as the common vocabulary
type for <code>pmr</code> allocators, rather than trying to force an allocator
for a single type.  See
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0339r6">P0339R6</a>
for more details.
</p>

<blockquote class="draft_wording"><pre>
struct AproxAggregate {
   std::pmr::string                  data1;
   std::pmr::string                  data2;
   std::pmr::string                  data3;
   std::pmr::polymorphic_allocator&lt;&gt; alloc;

   <ins>using allocator_type = std::pmr::polymorphic_allocator&lt;&gt;;</ins>

   <i>// Default, move, and copy constructors</i>

   AproxAggregate() = default;
   AproxAggregate(AproxAggregate&amp;&amp;) = default;
   AproxAggregate(AproxAggregate&amp;&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a); <i>// see below</i>

   AproxAggregate(AproxAggregate const&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a = {})
   : AproxAggregate(other.data1, other.data2, other.data3, a)
   {}

   <i>// Value constructors</i>

   explicit AproxAggregate(std::pmr::polymorphic_allocator&lt;&gt; a)
   : AproxAggregate(std::pmr::string{}, std::pmr::string{}, std::pmr::string{}, a)
   {}

   template &lt;typename T&gt;
      requires std::constructible_from&lt;std::pmr::string, T&gt;
   explicit AproxAggregate( T&amp;&amp; s
                          , std::pmr::polymorphic_allocator&lt;&gt; a = {}
                          )
   : AproxAggregate(std::forward&lt;T&gt;(s), std::pmr::string{}, std::pmr::string{}, a)
   {}

   template &lt;typename T1, typename T2&gt;
      requires std::constructible_from&lt;std::pmr::string, T1&gt;
           and std::constructible_from&lt;std::pmr::string, T2&gt;
   AproxAggregate( T1&amp;&amp; s1
                 , T2&amp;&amp; s2
                 , std::pmr::polymorphic_allocator&lt;&gt; a = {}
                 )
   : AproxAggregate(std::forward&lt;T1&gt;(s1), std::forward&lt;T2&gt;(s2), std::pmr::string{}, a)
   {}

   template &lt;typename T1, typename T2, typename T3&gt;
      requires std::constructible_from&lt;std::pmr::string, T1&gt;
           and std::constructible_from&lt;std::pmr::string, T2&gt;
           and std::constructible_from&lt;std::pmr::string, T3&gt;
   AproxAggregate( T1&amp;&amp; s1
                 , T2&amp;&amp; s2
                 , T3&amp;&amp; s3
                 , std::pmr::polymorphic_allocator&lt;&gt; a = {}
                 )
   : data1(std::forward&lt;T1&gt;(s1), a)
   , data2(std::forward&lt;T2&gt;(s2), a)
   , data3(std::forward&lt;T3&gt;(s3), a)
   , alloc(a)
   {}

   <i>// Allocator access</i>

   auto get_allocator() noexcept
     -&gt; std::pmr::polymorphic_allocator&lt;&gt; {
      return alloc;
   }

   <i>// Assignment operators</i>

   auto operator=(AproxAggregate&amp;&amp; other)
     -&gt; AproxAggregate &amp; {
      data1 = std::move(other.data1);
      data2 = std::move(other.data2);
      data3 = std::move(other.data3);
      // do not propagate the allocator

      return *this;
   }

   auto operator=(AproxAggregate const&amp; other)
     -&gt; AproxAggregate &amp; {
      data1 = other.data1;
      data2 = other.data2;
      data3 = other.data3;
      // do not propagate the allocator

      return *this;
   }

   <i>// Comparison operators</i>

   friend
   auto operator==(AproxAggregate const &amp;lhs, AproxAggregate const &amp;rhs) noexcept
     -&gt; bool
   {
       return lhs.data1 == rhs.data1
          and lhs.data2 == rhs.data2
          and lhs.data3 == rhs.data3;
       // and ignore the allocator
   }

   friend
   auto operator&lt;=&gt;(AproxAggregate const &amp; lhs, AproxAggregate const &amp; rhs) noexcept
     -&gt; std::strong_ordering {
       return lhs.data1 &lt; rhs.data1 ? std::strong_ordering::less
            : lhs.data1 &gt; rhs.data1 ? std::strong_ordering::greater
            : lhs.data2 &lt; rhs.data2 ? std::strong_ordering::less
            : lhs.data2 &gt; rhs.data2 ? std::strong_ordering::greater
            : lhs.data3 &lt; rhs.data3 ? std::strong_ordering::less
            : lhs.data3 &gt; rhs.data3 ? std::strong_ordering::greater
            :                         std::strong_ordering::equal;
       // and ignore the allocator
   }

};
</pre></blockquote>

<p>
If that seems like a lot of work, then you understand the motivation for this
paper!
</p>

<p>
First we add a data member to hold the allocator for this object, that will be
consistently supplied to all data members, and the <code>get_allocator</code>
member function so that clients can query for the allocator after construction.
This is important information to ensure that moves can happen efficiently, for
example.  We also add the typedef member for the <code>uses_allocator</code>
type trait to detect, advertising that this class is allocator-aware.
</p>

<p>
Then we add a set of constructor overloads that allow initialization like an
aggregate, where we can supply an initializer for each of the first 0-3
elements, followed by an optional trailing allocator that defaults to value
initialized.  We constrain each argument on constructability to ensure our type
returns the correct answer for the <code>is_constructible</code> type trait and
for the <code>constructible_form</code> concept itself.  We then use delegating
constructors to simplify concerns and ensure consistency is maintained,
perfectly forwarding each argument, and value initializing the "default"
arguments (which cannot be deduced) to ensure behavior consistent with
aggregate initialization.  Note that a value-initialized polymorphic allocator
will pick up the system supplied default memory resource.
</p>

<p>
Next we add the default, copy, and move constructors, as we can no longer rely
on implicit declaration.  In order to get the correct <code>explicit</code>
behavior for the default constructor, we have had to separate the
single-argument allocator constructor into the value constructors above, rather
than relying on default arguments.  The default constructor using the default
allocator has the correct implementation supplied behavior, as does the move
constructor, so those members can be defaulted.  However, for the copy
constructor we want to allow the user to supply an allocator, that can be
defaulted, so we implement this like the value constructors above, by using a
delegating constructor to the common implementation.  Note that this copy
constructor also serves as the move-with-allocator constructor; we will revisit
that topic after the next example, in order to complete both.
</p>

<p>
Then we address the assignment operators, that are implicitly deleted, as the
assignment operators of <code>std::pmr::polymorphic_allocator</code> are
deleted.  These operators are deleted to ensure that we do not accidentally
propagate the allocator, breaking the scoped model. Thus, we are forced to
always write the assignment operators, taking care to not try to assign the
allocator member, <code>alloc</code>.  The implementation is tedious but
straight forward, and vulnerable to falling out of date if the class is
modified in the future, like any other used supplied assignment operator.
We also take care to remember to move each member when implementing the move
assignment operator, a simple opportunity to introduce an error that is equally
easily detected by reasonable test driver coverage.
</p>

<p>
Finally we add the comparison operators to better describe a value semantic
type.  Again, the presence of the allocator member <code>alloc</code> means
we cannot simply rely on the default implementation.  We note that the
spaceship operator, while tedious, is exactly the kind of code where errors are
easily introduced by simple typos or ordering constraints, and not something
we would want to routinely writing for any types.
</p>

<p>
What has all this work brought us?  We can now write a much simpler usage
example!
</p>


<blockquote class="draft_wording"><pre>
std::pmr::polymorphic_allocator a2;
AproxAggregate x { a2 };
AproxAggregate x1{"Hello", a2};
AproxAggregate x2{"Hello", "World", a2};
AproxAggregate x3{"Hello", "World", "!", a2};
</pre></blockquote>

<p>
Note that this simplified usage example show how code is cleaned up with all
typical usage of this class, where allocators matter, highlighting the real
problem with aggregates at the point of use.
</p>

<p>
However, the key takeaway is that this class will behave properly as the
element type for any standard container.
</p>

<h4 id="5.a.simple">A simpler emulation</h4>
<p>
The <code>AproxAggregate</code> implementation is a lot of code, so the first
question we should ask is can we do better without changing the language,
knowing that we are dealing with a simple aggregate?  Fortunately, the answer
is "Yes!", as long as we are wrapping other allocator-aware members (or bases).
The key simplification is to use the allocator stored in one of the members
instead of tracking our own copy, which relies on that member in turn exposing
its allocator though a <code>get_allocator</code> member function.  Fortunately
this is the best practice encouraged by following the example of allocator
aware containers in the standard library, even if it cannot be strictly relied
on if we were writing a class template wrapping an arbitrary type
<code>T</code>.  As we are following the principles of the scoped allocator
model, we know that the allocator in each of our allocator-aware members
<em>must</em> be the same as for the complete object.  Hence, we can update the
definition of <code>get_allocator</code> and simply drop the redundant
allocator data member.  This allows us to lean into more default definitions
for members.
</p>

<blockquote class="draft_wording"><pre>
struct NotAnAggregate {
   std::pmr::string data1;
   std::pmr::string data2;
   std::pmr::string data3;

   using allocator_type = std::pmr::polymorphic_allocator&lt;&gt;;

   <i>// Default, move, and copy constructors</i>

   NotAnAggregate() = default;
   NotAnAggregate(NotAnAggregate&amp;&amp;) = default;
   NotAnAggregate(NotAnAggregate&amp;&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a); <i>// see below</i>

   <i>// Value constructors</i>

   NotAnAggregate(NotAnAggregate const&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a = {})
   : NotAnAggregate(other.data1, other.data2, other.data3, a)
   {}

   explicit NotAnAggregate(std::pmr::polymorphic_allocator&lt;&gt; a)
   : NotAnAggregate(std::pmr::string{}, std::pmr::string{}, std::pmr::string{}, a)
   {}

   template &lt;typename T&gt;
      requires std::constructible_from&lt;std::pmr::string, T&gt;
   explicit NotAnAggregate( T&amp;&amp; s
                          , std::pmr::polymorphic_allocator&lt;&gt; a = {}
                          )
   : NotAnAggregate(std::forward&lt;T&gt;(s), std::pmr::string{}, std::pmr::string{}, a)
   {}

   template &lt;typename T1, typename T2&gt;
      requires std::constructible_from&lt;std::pmr::string, T1&gt;
           and std::constructible_from&lt;std::pmr::string, T2&gt;
   NotAnAggregate( T1&amp;&amp; s1
                 , T2&amp;&amp; s2
                 , std::pmr::polymorphic_allocator&lt;&gt; a = {}
                 )
   : NotAnAggregate(std::forward&lt;T1&gt;(s1), std::forward&lt;T2&gt;(s2), std::pmr::string{}, a)
   {}

   template &lt;typename T1, typename T2, typename T3&gt;
      requires std::constructible_from&lt;std::pmr::string, T1&gt;
           and std::constructible_from&lt;std::pmr::string, T2&gt;
           and std::constructible_from&lt;std::pmr::string, T3&gt;
   NotAnAggregate( T1&amp;&amp; s1
                 , T2&amp;&amp; s2
                 , T3&amp;&amp; s3
                 , std::pmr::polymorphic_allocator&lt;&gt; a = {}
                 )
   : data1(std::forward&lt;T1&gt;(s1), a)
   , data2(std::forward&lt;T2&gt;(s2), a)
   , data3(std::forward&lt;T3&gt;(s3), a)
   {}

   <i>// Allocator access</i>

   auto get_allocator() noexcept
     -&gt; std::pmr::polymorphic_allocator&lt;&gt; {
      return <ins>data1.get_allocator()</ins>;
   }

   <i>// Assignment operators</i>

   auto operator=(NotAnAggregate &amp;&amp;     other) -&gt; NotAnAggregate &amp; = default;
   auto operator=(NotAnAggregate const&amp; other) -&gt; NotAnAggregate &amp; = default;

   <i>// Comparison operators</i>

   friend auto operator ==(NotAnAggregate const &amp;, NotAnAggregate const &amp;) -&gt; bool = default;
   friend auto operator&lt;=&gt;(NotAnAggregate const &amp;, NotAnAggregate const &amp;) -&gt; std::strong_ordering = default;
};
</pre></blockquote>

<p>
Note that while the class is much simpler, the usage example is entirely
unchanged, and we still have full support for standard containers.
</p>

<blockquote class="draft_wording"><pre>
std::pmr::polymorphic_allocator a3;
NotAnAggregate n { a3 };
NotAnAggregate n1{"Hello", a3};
NotAnAggregate n2{"Hello", "World", a3};
NotAnAggregate n3{"Hello", "World", "!", a3};
</pre></blockquote>

<h4 id="5.a.move">Move-construction with an allocator</h4>

<p>
There is one issue still to be addressed, and that is move construction where
an allocator is supplied.  At the moment, all such move construction will call
the copy constructor with an allocator, as that is what name-lookup/overload
resolution will select.  This is a good default behavior when the objects have
different allocators
(see <a href="#2.move_construct">Allocator-aware Move Construction</a>)
but misses an important optimization when the supplied allocator matches that
of the moving object at runtime.  In the ideal world we would be able to
delegate to the move or copy constructor via a ternary operator, but that is
not proposed, and certainly not available in C++23.  Hence, we will use the
following function template and idiom:
</p>

<blockquote class="draft_wording"><pre>
template &lt;typename T&gt;
auto move_construct_with_allocator( T&amp;&amp; source
                                  , std::pmr::polymorphic_allocator&lt;&gt; alloc)
  -&gt; T
{
   return alloc == source.get_allocator()
                 ? T(std::move(source))
                 : std::make_obj_using_allocator&lt;T&gt;(alloc, source);
}
</pre></blockquote>

<p>
Observe that this function always returns <em>by value</em> an object of type
<code>T</code> with the correct allocator installed.  If the allocator for
<code>source</code> matches <code>alloc</code> then the return value is move
constructed, otherwise the returned value is a copy using the supplied
allocator.  This factory function allows us to safely implement the missing
move-with-allocator constructors.
</p>

<blockquote class="draft_wording"><pre>
   AproxAggregate(AproxAggregate &amp;&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a = {})
   : AproxAggregate(move_construct_with_allocator(std::move(other), a))
   {}

   NotAnAggregate(NotAnAggregate &amp;&amp; other, std::pmr::polymorphic_allocator&lt;&gt; a = {})
   : NotAnAggregate(move_construct_with_allocator(std::move(other), a))
   {}
</pre></blockquote>

<p>
Observe that the allocator is passed to the factory function
<code>move_construct_with_allocator</code>, and the result is passed directly
to the move constructor.  With the value materialization guarantees of C++17,
this should elide creating an actual temporary to move from.
</p>

<h4 id="5.a.lang">Proposed simplification</h4>

<p>
So how would this type be expressed using our new language features.  Here, we
will assume <code>std2::string</code> is a new type, exactly like
<code>std::string</code> other than relying on our new language support for its
allocator.  Remember, this is for illustration purposes only, we are not
actively recommending <code>std2</code> or other library changes at this time.
</p>

<blockquote class="draft_wording"><pre>
struct ScopeAggregate {
   <ins>std2</ins>::string data1;
   <ins>std2</ins>::string data2;
   <ins>std2</ins>::string data3;

   friend auto operator ==(ScopeAggregate const &amp;, ScopeAggregate const &amp;) -&gt; bool = default;
   friend auto operator&lt;=&gt;(ScopeAggregate const &amp;, ScopeAggregate const &amp;) -&gt; std::strong_ordering = default;
};
</pre></blockquote>

<p>
That looks remarkably like the original simple aggregate that started this
example, other than the switch of string types.  However, this aggregate will
correctly work with containers (implemented using the same language support)
and can report the object's allocator through the implicitly declared and
defined friend function <code>allocator_of</code>.  Observe that we can finally
rely on the classic <i>rule of zero</i> when implementing allocator-aware types.
</p>

<p>
How does our usage example change?
</p>

<blockquote class="draft_wording"><pre>
std::pmr::polymorphic_allocator a4;
ScopeAggregate s  <ins>using a4</ins>;
ScopeAggregate s1 <ins>using a4</ins> {"Hello"};
ScopeAggregate s2 <ins>using a4</ins> {"Hello", "World"};
ScopeAggregate s3 <ins>using a4</ins> {"Hello", "World", "!"};
</pre></blockquote>

<p>
The usage example looks more like the simplified usage examples of the emulated
types, but the position of the allocator argument has moved, using our proposed
syntax.  We can omit the <code>using</code> expressions to just accept the
system supplied default memory resource.
</p>

<p>
Finally, we can look at the usage example for <code>std::array</code> with the
proposed facilities.  Note that we really do mean <em><code>std</code></em>
array, and not some funky new <code>std2</code> array type.
</p>

<blockquote class="draft_wording"><pre>
using StdArrayString = std::array&lt;<ins>std2</ins>::string, 3&gt;;

std::pmr::polymorphic_allocator a5;
StdArrayString as2  <ins>using a5</ins>;
StdArrayString as21 <ins>using a5</ins> {"Hello"};
StdArrayString as22 <ins>using a5</ins> {"Hello", "World"};
StdArrayString as23 <ins>using a5</ins> {"Hello", "World", "!"};

<i>// Accept the default allocator</i>

StdArrayString as2d;
StdArrayString as2d1 {"Hello"};
StdArrayString as2d2 {"Hello", "World"};
StdArrayString as2d3 {"Hello", "World", "!"};
</pre></blockquote>

<p>
This is a demonstration that there are generally fewer gotchas with this
proposal, such as requiring unexpected additional braces.  Observe that the
initialization with or without allocators look simple and consistent with each
other.
</p>


<h3 id="5.vector"><code>std::vector</code></h3>
<p>
For a familiar example, let us take a look at how the current
<code>std::vector</code> would be revised using these new language features.
Note that we do not propose making such a change to the library as part of this
proposal, due to obvious ABI (and API) compatibility concerns; this is an
illustration of how existing user code might be updated once these features are
available, or alternatively, where the simplicity of an implementation using
our proposed language support lies.  We are using namespace <code>std2</code>
as per our convention to distinguish new code from true <code>std</code>
specification.
</p>

<blockquote class="draft_wording"><pre>
namespace <ins>std2</ins> {  <i>// For illustrative purposes only, not a proposal</i>

  <i>// 24.3.11, class template vector</i>
  template&lt;class T<del>, class Allocator = allocator&lt;T&gt;</del>&gt; class vector;
  template&lt;class T<del>, class Allocator</del>&gt;
    constexpr bool operator==(const vector&lt;T<del>, Allocator</del>&gt;&amp; x, const vector&lt;T<del>, Allocator</del>&gt;&amp; y);
  template&lt;class T<del>, class Allocator</del>&gt;
    constexpr synth-three-way-result&lt;T&gt; operator&lt;=&gt;(const vector&lt;T<del>, Allocator</del>&gt;&amp; x,
                                                    const vector&lt;T<del>, Allocator</del>&gt;&amp; y);
  template&lt;class T<del>, class Allocator</del>&gt;
    constexpr void swap(vector&lt;T<del>, Allocator</del>&gt;&amp; x, vector&lt;T<del>, Allocator</del>&gt;&amp; y)<ins>;</ins>
      <del>noexcept(noexcept(x.swap(y)));</del>

  <i>// 24.3.11.6, erasure</i>
  template&lt;class T, <del>class Allocator,</del> class U&gt;
    <del>constexpr typename vector&lt;T, Allocator&gt;::size_type</del>
    <ins>constexpr size_t</ins> erase(vector&lt;T<del>, Allocator</del>&gt;&amp; c, const U&amp; value);
  template&lt;class T, <del>class Allocator,</del> class Predicate&gt;
    <del>constexpr typename vector&lt;T, Allocator&gt;::size_type</del>
    <ins>constexpr size_t</ins>  erase_if(vector&lt;T<del>, Allocator</del>&gt;&amp; c, Predicate pred);

  <del>namespace pmr {</del>
    <del>template&lt;class T&gt;</del>
      <del>using vector = std::vector&lt;T, polymorphic_allocator&lt;T&gt;&gt;;</del>
  <del>}</del>

  <i>// 24.3.12, specialization of vector for bool</i>
  <i>// 24.3.12.1, partial class template specialization vector&lt;bool<del>, Allocator</del>&gt;</i>
  <del>template&lt;class Allocator&gt;</del>
  class vector&lt;bool<del>, Allocator</del>&gt;;

  <i>// hash support</i>
  template&lt;class T&gt; struct hash;
  <del>template&lt;class Allocator&gt;</del> struct hash&lt;vector&lt;bool<del>, Allocator</del>&gt;&gt;;

  <del><i>// 24.3.12.2, formatter specialization for vector&lt;bool&gt;</i></del>
  <del>template&lt;class T&gt;</del>
  <del>inline constexpr bool <i>is-vector-bool-reference</i> = <i>see below</i>; <i>// exposition only</i></del>

  template&lt;<del>class T,</del> class charT&gt; <del>requires <i>is-vector-bool-reference</i>&lt;T&gt;</del>
    struct formatter&lt;<del>T</del><ins>vector&lt;bool&gt;::reference</ins>, charT&gt;;
}
</pre></blockquote>

<p>
In addition to simplifying vector by eliminating one of the type parameters, we
see a few side-effects as a result:
</p>
<ul>
  <li>
  The allocator is fully determined, so there is no more type dependency when
  providing certain types, such as the <code>size_type</code> needed for the
  consistent erasure API.
  </li>
  <li>
  <code>vector&lt;bool&gt;::reference</code> is no longer type-dependent on the
  allocator, so can simply be named, entirely eliminating an exposition-only
  concept.
  <li>
  The <code>hash</code> template specialization is now an explicit template
  specialization rather than a partial template specialization.  This opens up
  more freedom for where the definitions may be placed in source.
  </li>
  <li>
  The free-function <code>swap</code> is simplified by losing its
  <code>noexcept</code> specification; this might be seen as losing a feature,
  but it is already the case for any scoped-type model, such as
  <code>std::pmr::vector</code> today, and it is easier to see.  This lack of
  <code>noexcept</code> could be ameliorated with the introduction of a runtime
  exception specification, but we are not highlighting that extension here, nor
  <code>operator swap</code>.
  </li>
  <li>
  Finally, the aliases in namespace <code>pmr</code> serve no purpose, so are
  eliminated.
  </li>
</ul>

<blockquote class="draft_wording"><pre>
namespace <ins>std2</ins> {  <i>// For illustrative purposes only, not a proposal</i>
template&lt;class T<del>, class Allocator = allocator&lt;T&gt;</del>&gt;
  class vector {
  public:
    <i>// types</i>
    using value_type             = T;
    <del>using allocator_type         = Allocator;</del>
    using pointer                = <del>typename allocator_traits&lt;Allocator&gt;::pointer</del><ins>value_type*</ins>;
    using const_pointer          = <del>typename allocator_traits&lt;Allocator&gt;::const_pointer</del><ins>const value_type*</ins>;
    using reference              = value_type&amp;;
    using const_reference        = const value_type&amp;;
    using size_type              = <ins>size_t</ins><del><i>implementation-defined</i>; // see 24.2</del>
    using difference_type        = <ins>ptrdiff_t</ins><del><i>implementation-defined</i>; // see 24.2</del>
    using iterator               = <i>implementation-defined</i>; // see 24.2
    using const_iterator         = <i>implementation-defined</i>; // see 24.2
    using reverse_iterator       = std::reverse_iterator&lt;iterator&gt;;
    using const_reverse_iterator = std::reverse_iterator&lt;const_iterator&gt;;

    <i>// 24.3.11.2, construct/copy/destroy</i>
    constexpr vector() noexcept<del>(noexcept(Allocator())) : vector(Allocator()) { }</del><ins>;</ins>
    <del>constexpr explicit vector(const Allocator&amp;) noexcept;</del>
    constexpr explicit vector(size_type n<del>, const Allocator&amp; = Allocator()</del>);
    constexpr vector(size_type n, const T&amp; value<del>, const Allocator&amp; = Allocator()</del>);
    template&lt;class InputIterator&gt;
      constexpr vector(InputIterator first, InputIterator last<del>, const Allocator&amp; = Allocator()</del>);
    template&lt;<i>container-compatible-range</i>&lt;T&gt; R&gt;
      constexpr vector(from_range_t, R&amp;&amp; rg<del>, const Allocator&amp; = Allocator()</del>);
    constexpr vector(const vector&amp; x);
    constexpr vector(vector&amp;&amp;) noexcept;
    <del>constexpr vector(const vector&amp;, const type_identity_t&lt;Allocator&gt;&amp;);</del>
    <del>constexpr vector(vector&amp;&amp;, const type_identity_t&lt;Allocator&gt;&amp;);</del>
    constexpr vector(initializer_list&lt;T&gt;<del>, const Allocator&amp; = Allocator()</del>);
    constexpr ~vector();

    constexpr vector&amp; operator=(const vector&amp; x);
    constexpr vector&amp; operator=(vector&amp;&amp; x)<ins>;</ins>
      <del>noexcept(allocator_traits&lt;Allocator&gt;::propagate_on_container_move_assignment::value ||</del>
               <del>allocator_traits&lt;Allocator&gt;::is_always_equal::value);</del>
    constexpr vector&amp; operator=(initializer_list&lt;T&gt;);
    template&lt;class InputIterator&gt;
    constexpr void assign(InputIterator first, InputIterator last);
    template&lt;<i>container-compatible-range</i>&lt;T&gt; R&gt;
      constexpr void assign_range(R&amp;&amp; rg);
    constexpr void assign(size_type n, const T&amp; u);
    constexpr void assign(initializer_list&lt;T&gt;);
    <del>constexpr allocator_type get_allocator() const noexcept;</del>

    <i>// iterators</i>
    constexpr iterator               begin() noexcept;
    constexpr const_iterator         begin() const noexcept;
    constexpr iterator               end() noexcept;
    constexpr const_iterator         end() const noexcept;
    constexpr reverse_iterator       rbegin() noexcept;
    constexpr const_reverse_iterator rbegin() const noexcept;
    constexpr reverse_iterator       rend() noexcept;
    constexpr const_reverse_iterator rend() const noexcept;

    constexpr const_iterator         cbegin() const noexcept;
    constexpr const_iterator         cend() const noexcept;
    constexpr const_reverse_iterator crbegin() const noexcept;
    constexpr const_reverse_iterator crend() const noexcept;

    <i>// 24.3.11.3, capacity</i>
    [[nodiscard]] constexpr bool empty() const noexcept;
    constexpr size_type size() const noexcept;
    constexpr size_type max_size() const noexcept;
    constexpr size_type capacity() const noexcept;
    constexpr void      resize(size_type sz);
    constexpr void      resize(size_type sz, const T&amp; c);
    constexpr void      reserve(size_type n);
    constexpr void      shrink_to_fit();

    <i>// element access</i>
    constexpr reference
    constexpr const_reference operator[](size_type n) const;
    constexpr const_reference at(size_type n) const;
    constexpr reference       at(size_type n);
    constexpr reference       front();
    constexpr const_reference front() const;
    constexpr reference       back();
    constexpr const_reference back() const;

    <i>// 24.3.11.4, data access</i>
    constexpr T* data() noexcept;
    constexpr const T* data() const noexcept;

    <i>// 24.3.11.5, modifiers</i>
    template&lt;class... Args&gt;
      constexpr reference emplace_back(Args&amp;&amp;... args);
    constexpr void push_back(const T&amp; x);
    constexpr void push_back(T&amp;&amp; x);
    template&lt;<i>container-compatible-range</i>&lt;T&gt; R&gt;
      constexpr void append_range(R&amp;&amp; rg);
    constexpr void pop_back();

    template&lt;class... Args&gt;
      constexpr iterator emplace(const_iterator position, Args&amp;&amp;... args);
    constexpr iterator insert(const_iterator position, const T&amp; x);
    constexpr iterator insert(const_iterator position, T&amp;&amp; x);
    constexpr iterator insert(const_iterator position, size_type n, const T&amp; x);
    template&lt;class InputIterator&gt;
      constexpr iterator insert(const_iterator position,
                                InputIterator first, InputIterator last);
    template&lt;<i>container-compatible-range</i>&lt;T&gt; R&gt;
      constexpr iterator insert_range(const_iterator position, R&amp;&amp; rg);
    constexpr iterator insert(const_iterator position, initializer_list&lt;T&gt; il);
    constexpr iterator erase(const_iterator position);
    constexpr iterator erase(const_iterator first, const_iterator last);
    constexpr void     swap(vector&amp;)<ins>;</ins>
      <del>noexcept(allocator_traits&lt;Allocator&gt;::propagate_on_container_swap::value ||</del>
               <del>allocator_traits&lt;Allocator&gt;::is_always_equal::value);</del>
    constexpr void     clear() noexcept;
  };

  template&lt;class InputIterator<del>, class Allocator = allocator&lt;<i>iter-value-type</i>&lt;InputIterator&gt;&gt;</del>&gt;
    vector(InputIterator, InputIterator<del>, Allocator = Allocator()</del>)
      -&gt; vector&lt;<i>iter-value-type</i>&lt;InputIterator&gt;<del>, Allocator</del>&gt;;

  template&lt;ranges::input_range R<del>, class Allocator = allocator&lt;ranges::range_value_t&lt;R&gt;&gt;</del>&gt;
    vector(from_range_t, R&amp;&amp;<del>, Allocator = Allocator()</del>)
      -&gt; vector&lt;ranges::range_value_t&lt;R&gt;<del>, Allocator</del>&gt;;
}
</pre></blockquote>

<p>
When we look at the class template definition, it should be no surprise that
the main impact is on the constructors.  As the container is itself responsible
for allocating its members (and propagating the allocator), there are no
allocator arguments to any other function member - the allocator of the
container is implicit in each call, via <code>this</code>, and has been since
C++98.  Note that the copy and move constructors simplify, no longer needing a
separate allocator-aware overload, and so avoiding template tricks to support
deduction guides.  Similarly, there is no longer an <code>explicit</code>
constructor taking a single allocator argument as the allocator is not passed
through the constructor argument list, and so the default constructor is
unconditionally <code>noexcept</code>.
</p>
<p>
There are a couple of simplifications taking the type dependency out of type
aliases, and the <code>get_allocator</code> function member is replaced by the
(implicit) friend function <code>allocator_of</code>.  The function member
<code>swap</code> loses its exception specification, just like the
free-function.
</p>
<p>
Finally, the deduction guides simplify as there is no optional allocator
argument to the constructors they deduce from.
</p>


<h3 id="5.unordered"><code>std::unordered_map</code></h3>
<p>
For a second example, let us take a look at how the current
<code>std::unordered_map</code> would be revised using these new language
features.  Note again that we do not propose making such a change to the
library as part of this proposal, due to obvious ABI (and API) compatibility
concerns; this is an illustration of how existing user code might be updated
once these features are available.  We are using namespace <code>std2</code> as
per our convention to distinguish new code from true <code>std</code>
specification.
</p>

<blockquote class="draft_wording"><pre>
namespace <ins>std2</ins> {  <i>// For illustrative purposes only, not a proposal</i>
template&lt;class Key,
         class T,
         class Hash = hash&lt;Key&gt;,
         class Pred = equal_to&lt;Key&gt;<ins>&gt;</ins><del>,</del>
         <del>class Allocator = allocator&lt;pair&lt;const Key, T&gt;&gt;&gt;</del>
class unordered_map {
public:
  // types
  using key_type                = Key;
  using mapped_type             = T;
  using value_type              = pair&lt;const Key, T&gt;;
  using hasher                  = Hash;
  using key_equal               = Pred;
  <del>using allocator_type          = Allocator;</del>
  using pointer                 = <del>typename allocator_traits&lt;Allocator&gt;::pointer</del><ins>value_type*</ins>;
  using const_pointer           = <del>typename allocator_traits&lt;Allocator&gt;::const_pointer</del><ins>const value_type*</ins>;
  using reference               = value_type&amp;;
  using const_reference         = const value_type&amp;;
  using size_type               = <ins>size_t</ins><del><i>implementation-defined</i>; <i>// see 24.2</i></del>
  using difference_type         = <ins>ptrdiff_t</ins><del><i>implementation-defined</i>; <i>// see 24.2</i></del>
  using iterator                = <i>implementation-defined</i>; <i>// see 24.2</i>
  using const_iterator          = <i>implementation-defined</i>; <i>// see 24.2</i>
  using local_iterator          = <i>implementation-defined</i>; <i>// see 24.2</i>
  using const_local_iterator    = <i>implementation-defined</i>; <i>// see 24.2</i>
  using node_type               = <i>unspecified;</i>
  using insert_return_type      = <i>insert-return-type</i>&lt;iterator, node_type&gt;;

  <i>// 24.5.4.2, construct/copy/destroy unordered_map();</i>
  explicit unordered_map(size_t<del>ype</del> n,
                         const hasher&amp; hf = hasher(),
                         const key_equal&amp; eql = key_equal()<del>,</del>
                         <del>const allocator_type&amp; a = allocator_type()</del>);

  template&lt;class InputIterator&gt;
    unordered_map(InputIterator f, InputIterator l,
                  size_t<del>ype</del> n = <i>see below</i>,
                  const hasher&amp; hf = hasher(),
                  const key_equal&amp; eql = key_equal()<del>,</del>
                  <del>const allocator_type&amp; a = allocator_type()</del>);

  template&lt;container-compatible-range&lt;value_type&gt; R&gt;
    unordered_map(from_range_t, R&amp;&amp; rg, size_t<del>ype</del> n = see below,
                  const hasher&amp; hf = hasher(), const key_equal&amp; eql = key_equal()<del>,</del>
                  <del>const allocator_type&amp; a = allocator_type()</del>);

  unordered_map(const unordered_map&amp;);
  unordered_map(unordered_map&amp;&amp;);
  <del>explicit unordered_map(const Allocator&amp;);</del>
  <del>unordered_map(const unordered_map&amp;, const type_identity_t&lt;Allocator&gt;&amp;);</del>
  <del>unordered_map(unordered_map&amp;&amp;, const type_identity_t&lt;Allocator&gt;&amp;);</del>

  unordered_map(initializer_list&lt;value_type&gt; il,
                size_t<del>ype</del> n = <i>see below</i>,
                const hasher&amp; hf = hasher(),
                const key_equal&amp; eql = key_equal()<del>,</del>
                <del>const allocator_type&amp; a = allocator_type()</del>);

  <del>unordered_map(size_type n, const allocator_type&amp; a)</del>
    <del>: unordered_map(n, hasher(), key_equal(), a) { }</del>
  <del>unordered_map(size_type n, const hasher&amp; hf, const allocator_type&amp; a)</del>
    <del>: unordered_map(n, hf, key_equal(), a) { }</del>
  <del>template&lt;class InputIterator&gt;</del>
    <del>unordered_map(InputIterator f, InputIterator l, size_type n, const allocator_type&amp; a)</del>
      <del>: unordered_map(f, l, n, hasher(), key_equal(), a) { }</del>
  <del>template&lt;class InputIterator&gt;</del>
    <del>unordered_map(InputIterator f, InputIterator l, size_type n, const hasher&amp; hf,</del>
                  <del>const allocator_type&amp; a)</del>
    <del>: unordered_map(f, l, n, hf, key_equal(), a) { }</del>
  <del>template&lt;container-compatible-range&lt;value_type&gt; R&gt;</del>
    <del>unordered_map(from_range_t, R&amp;&amp; rg, size_type n, const allocator_type&amp; a)</del>
      <del>: unordered_map(from_range, std::forward&lt;R&gt;(rg), n, hasher(), key_equal(), a) { }</del>
  <del>template&lt;container-compatible-range&lt;value_type&gt; R&gt;</del>
    <del>unordered_map(from_range_t, R&amp;&amp; rg, size_type n, const hasher&amp; hf, const allocator_type&amp; a)</del>
      <del>: unordered_map(from_range, std::forward&lt;R&gt;(rg), n, hf, key_equal(), a) { }</del>
  <del>unordered_map(initializer_list&lt;value_type&gt; il, size_type n, const allocator_type&amp; a)</del>
    <del>: unordered_map(il, n, hasher(), key_equal(), a) { }</del>
  <del>unordered_map(initializer_list&lt;value_type&gt; il, size_type n, const hasher&amp; hf,</del>
                <del>const allocator_type&amp; a)</del>
    <del>: unordered_map(il, n, hf, key_equal(), a) { }</del>

  ~unordered_map();

  unordered_map&amp; operator=(const unordered_map&amp;);
  unordered_map&amp; operator=(unordered_map&amp;&amp;)<ins>;</ins>
    <del>noexcept(allocator_traits&lt;Allocator&gt;::is_always_equal::value &amp;&amp;</del>
             <del>is_nothrow_move_assignable_v&lt;Hash&gt; &amp;&amp;</del>
             <del>is_nothrow_move_assignable_v&lt;Pred&gt;);</del>
  unordered_map&amp; operator=(initializer_list&lt;value_type&gt;);

  <del>allocator_type get_allocator() const noexcept;</del>

  <i>// ... member function details omitted as unchanged ...</i>
};

template&lt;class InputIterator,
         class Hash = hash&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;,
         class Pred = equal_to&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;<del>,</del>
         <del>class Allocator = allocator&lt;<i>iter-to-alloc-type</i>&lt;InputIterator&gt;&gt;</del>&gt;
  unordered_map(InputIterator, InputIterator, <del>typename <i>see below</i>::size_type</del><ins>size_t</ins> = <i>see below</i>,
                Hash = Hash(), Pred = Pred()<del>, Allocator = Allocator()</del>)
    -&gt; unordered_map&lt;<i>iter-key-type</i>&lt;InputIterator&gt;, <i>iter-mapped-type</i>&lt;InputIterator&gt;, Hash, Pred<del>, Allocator</del>&gt;;

template&lt;ranges::input_range R, class Hash = hash&lt;<i>range-key-type</i>&lt;R&gt;&gt;, class Pred = equal_to&lt;<i>range-key-type</i>&lt;R&gt;&gt;<del>,</del>
         <del>class Allocator = allocator&lt;<i>range-to-alloc-type</i>&lt;R&gt;&gt;</del>&gt;
  unordered_map(from_range_t, R&amp;&amp;, <del>typename <i>see below</i>::size_type</del><ins>size_t</ins> = <i>see below</i>,
                Hash = Hash(), Pred = Pred()<del>, Allocator = Allocator()</del>)
    -&gt; unordered_map&lt;<i>range-key-type</i>&lt;R&gt;, <i>range-mapped-type</i>&lt;R&gt;, Hash, Pred<del>, Allocator</del>&gt;;

template&lt;class Key, class T, class Hash = hash&lt;Key&gt;,
         class Pred = equal_to&lt;Key&gt;<del>, class Allocator = allocator&lt;pair&lt;const Key, T&gt;&gt;</del>&gt;
  unordered_map(initializer_list&lt;pair&lt;Key, T&gt;&gt;,
                <del>typename <i>see below</i>::size_type</del><ins>size_t</ins> = <i>see below</i>, Hash = Hash(), Pred = Pred()<del>, Allocator = Allocator()</del>)
    -&gt; unordered_map&lt;Key, T, Hash, Pred<del>, Allocator</del>&gt;;

<del>template&lt;class InputIterator, class Allocator&gt;</del>
  <del>unordered_map(InputIterator, InputIterator, typename <i>see below</i>::size_type, Allocator)</del>
    <del>-&gt; unordered_map&lt;<i>iter-key-type</i>&lt;InputIterator&gt;, <i>iter-mapped-type</i>&lt;InputIterator&gt;,</del>
                     <del>hash&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;,</del>
                     <del>equal_to&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;, Allocator&gt;;</del>

<del>template&lt;class InputIterator, class Allocator&gt;</del>
  <del>unordered_map(InputIterator, InputIterator, Allocator)</del>
    <del>-&gt; unordered_map&lt;<i>iter-key-type</i>&lt;InputIterator&gt;, <i>iter-mapped-type</i>&lt;InputIterator&gt;,</del>
                     <del>hash&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;,</del>
                     <del>equal_to&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;, Allocator&gt;;</del>

<del>template&lt;class InputIterator, class Hash, class Allocator&gt;</del>
  <del>unordered_map(InputIterator, InputIterator, typename <i>see below</i>::size_type, Hash, Allocator)</del>
    <del>-&gt; unordered_map&lt;<i>iter-key-type</i>&lt;InputIterator&gt;, <i>iter-mapped-type</i>&lt;InputIterator&gt;, Hash, equal_to&lt;<i>iter-key-type</i>&lt;InputIterator&gt;&gt;, Allocator&gt;;</del>

<del>template&lt;ranges::input_range R, class Allocator&gt;</del>
  <del>unordered_map(from_range_t, R&amp;&amp;, typename <i>see below</i>::size_type, Allocator)</del>
    <del>-&gt; unordered_map&lt;<i>range-key-type</i>&lt;R&gt;, <i>range-mapped-type</i>&lt;R&gt;, hash&lt;<i>range-key-type</i>&lt;R&gt;&gt;, equal_to&lt;<i>range-key-type</i>&lt;R&gt;&gt;, Allocator&gt;;</del>

<del>template&lt;ranges::input_range R, class Allocator&gt;</del>
  <del>unordered_map(from_range_t, R&amp;&amp;, Allocator)</del>
    <del>-&gt; unordered_map&lt;<i>range-key-type</i>&lt;R&gt;, <i>range-mapped-type</i>&lt;R&gt;, hash&lt;<i>range-key-type</i>&lt;R&gt;&gt;, equal_to&lt;<i>range-key-type</i>&lt;R&gt;&gt;, Allocator&gt;;</del>

<del>template&lt;ranges::input_range R, class Hash, class Allocator&gt;</del>
  <del>unordered_map(from_range_t, R&amp;&amp;, typename <i>see below</i>::size_type, Hash, Allocator)</del>
    <del>-&gt; unordered_map&lt;<i>range-key-type</i>&lt;R&gt;, <i>range-mapped-type</i>&lt;R&gt;, Hash, equal_to&lt;<i>range-key-type</i>&lt;R&gt;&gt;, Allocator&gt;;</del>

<del>template&lt;class Key, class T, class Allocator&gt;</del>
  <del>unordered_map(initializer_list&lt;pair&lt;Key, T&gt;&gt;, typename <i>see below</i>::size_type,</del>
                <del>Allocator)</del>
    <del>-&gt; unordered_map&lt;Key, T, hash&lt;Key&gt;, equal_to&lt;Key&gt;<del>, Allocator</del>&gt;;</del>

<del>template&lt;class Key, class T, class Allocator&gt;</del>
  <del>unordered_map(initializer_list&lt;pair&lt;Key, T&gt;&gt;, Allocator)</del>
    <del>-&gt; unordered_map&lt;Key, T, hash&lt;Key&gt;, equal_to&lt;Key&gt;, Allocator&gt;;</del>

<del>template&lt;class Key, class T, class Hash, class Allocator&gt;</del>
  <del>unordered_map(initializer_list&lt;pair&lt;Key, T&gt;&gt;, typename <i>see below</i>::size_type, Hash,</del>
                <del>Allocator)</del>
    <del>-&gt; unordered_map&lt;Key, T, Hash, equal_to&lt;Key&gt;, Allocator&gt;;</del>

// swap
template&lt;class Key, class T, class Hash, class Pred<del>, class Alloc</del>&gt;
  void swap(unordered_map&lt;Key, T, Hash, Pred<del>, Alloc</del>&gt;&amp; x,
            unordered_map&lt;Key, T, Hash, Pred<del>, Alloc</del>&gt;&amp; y)<ins>;</ins>
    <del>noexcept(noexcept(x.swap(y)));</del>
}
</pre></blockquote>

<p>
As you can see, the requirement for every possible initialization to support an
allocator has a more profound impact on <code>std::unordered_map</code>, due
to the number of overloads supporting multiple default arguments.  Once
allocators are implicit we eliminate almost 2/3 of the constructors, dropping
from 17 down to 6.  Similarly, 3/4 of the deduction guides are purely to deal
with allocators going into that overload set, so their number drops from 12 to
3.  Two exposition-only concepts used by deduction guides can also be
eliminated, <i>iter-to-alloc-type</i> and <i>range-to-alloc-type</i>, and the
special <i>see below</i> wording to deduce <code>size_type</code> from the
resulting template instantiation is no longer required, further simplifying the
implementation.
</p>

<h2 id="6.0">Open Questions</h2>
<p>
Several other approaches were considered and rejected by the authors.  Here we
record those ideas and the reasoning by which they were set aside.
</p>


<h3 id="6.union">Support for <code>union</code>s and <code>std::variant</code></h3>
<p>
We would like to efficiently represent <code>union</code>s and
<code>variant</code>s that comprise allocator-enabled types.  This is
relatively simple to solve for the case that all the alternatives are
allocator-enabled, but in the case that some of the alternatives are not so
enabled, we must store a copy of the allocator somewhere.  In the ideal world,
we would not store a redundant copy of the allocator when the active element is
allocator-enabled though.  It is expected that there will be some kind of
extension facility for users to customize their allocator storage, and then
overload the <code>allocator_of</code> friend function to return the right
allocator in all circumstances.
</p>


<h3 id="6.optional">Support for <code>std::optional</code></h3>
<p>
The case for <code>optional</code> is the same as for <code>variant</code>, if
we consider <code>optional&lt;T&gt;</code>'s fundamental equivalence to
<code>std::variant&lt;T, std::monostate&gt;</code>, a variant with one
allocator-enabled alternative and one non-allocator-enabled alternative.
</p>


<h3 id="6.factory">Explicit Factory Functions</h3>
<p>
Sometimes there will be a desire to pass an allocator to be used within a
factory function that does not return an allocator-enabled object, for example,
a factory function returning a smart pointer.  We expect there will be a need
to supply an allocator-enabled overload to such an <i>explicit factory
function</i>.  This question is deliberately deferred until after the basic
idea is proven successful though, as integrating with the world of overload
resolution is anything but a trivial task.
</p>


<h3 id="6.expressions"><code>using</code> Beyond Initialization Expressions</h3>
<p>
There is clearly a desire to support <code>using</code> clauses more widely
than just variable declarations, especially once explicit factory functions
are available.  However, there are a variety of concerns around consistency
and ambiguity that easily derail a discussion, so this topic is deferred until
the basic model is proven.
</p>

<p>
Control over temporaries created by subexpressions, parameters passed to
functions, and allocators used within entire subexpressions all seem like
interesting use cases that should be addressed eventually.
</p>


<h3 id="6.move-only">Move-only Scoped Types</h3>
<p>
The default implementation of initialization from an rvalue with a supplied
allocator requires a type to be copy constructible.  This is not always the
case, for example:
</p>

<blockquote class="draft_wording"><pre>
struct MyPair {
   std2::vector&lt;int&gt;    first;
   std::unique_ptr&lt;int&gt; second;
};
</pre></blockquote>

<p>
Here we would like move-with-allocator construction to either move or make a
copy of the <code>first</code> vector, per our current proposal.  However, the
<code>second</code> member is not allocator enabled, and cannot be copied, so
we would like to move this member instead.
</p>

<p>
This problem brings us back to the issue of having overloads for a function,
in this case the move constructor, both with and without the hidden
<code>using</code> allocator, so is beyond the reach of the initial simplified
model of this paper.
</p>


<h3 id="6.scoped">Scoped Types Beyond a Single Allocator Type</h3>
<p>
We have extensive experience applying the scoped model to allocators, and we
have often seen the benefits of its use for reasoning about object lifetime and
leveraging local memory arenas.
</p>

<p>
Support for more similarly scoped properties, along with the questions that
would arise of how they might interoperate, seem like an inevitable requirement
for this proposal.
</p>

<p>
The first such additional scoped property would likely be other allocator
types.  We're aware of libraries that use non-<code>pmr</code> polymorphic
memory resource handles to manage, for example, GPU memory or similar pools
that <code>pmr</code> does not lend itself to.  Allowing this facility to
support both types of allocators side by side would be beneficial.
</p>

<p>
In addition, we would expect a similar facility to benefit potential use for
telemetry, logging, and execution resources. Tying objects to particular
threads or cores seems like a particular case that might match the same model
we use to tie objects to specific regions in memory.  We hope to gather such
additional use cases to have that guide the further evolution of our design.
</p>


<h3 id="6.breakage">Controlled Breaking of the Scoped Model</h3>
<p>
Allowing types to assume the guarantees of the scoped allocator model is our
primary goal.  Experience has taught us, however, that there are exceptions to
every rule, and a facility by which the implicit rules can be subverted,
especially for specific bases and members, seems like it will be necessary to
cover all potential use cases.
</p>


<!--
<h2 id="7.0">Integrated Proposed Wording</h2>

<p>
Placeholder edits of the right form to copy for subsequent edits, but copied
from an entirely unrelated proposal.
</p>

<p>
Plan to take Josh's preliminary wording and place it here as a preview of what
our proposal might look like.
</p>

<blockquote class="proposed_wording">
<h4><ins>C.1 C++ and ISO C++ 2023</ins></h4>
<ol>
<li><ins>
This subclause lists the differences between C++ and ISO C++ 2020 (ISO/IEC
14882:2020, Programming Languages &mdash; C++), by the chapters of this
document.
</ins></li>
</ol>

<h4><ins>C.1.X Clause 7: Expressions [diff.cpp20.expr]</ins></h4>
<p><ins>
<b>Affected subclause:</b> 7.4</ins>
<i>Note for editors: [expr.arith.conv]</i>
</p>

<p>
<ins>
<b>Change:</b> Unscoped enumerations do not implicitly promote to integral type
in expressions with floating point types.
</ins>
</p>

<p>
<ins>
<b>Rationale:</b>
<blockquote class="note">
Provide rationale
</blockquote>
</ins>
</p>

<p><ins>
<b>Effect on original feature:</b> A valid C++ 2020 involving both an unscoped
enumeration and a floating point value will be rejected as ill-formed in this
International Standard.  Either argument could be explicitly converted with a
cast, or the enumeration could be explicitly promoted to an integer with unary
operator +, for no change of meaning since C++ 2020.  [<i>Example:</i>
</ins></p>
<blockquote><pre>
<ins>enum multipliers { gigaseconds = 1'000'000 };</ins>
<ins>double century_ish = 3.14 *  gigaseconds;  <i>// ill-formed; previously well-formed</i></ins>
<ins>double century_est = 3.14 * +gigaseconds;  <i>// OK</i></ins>
</pre></blockquote>
<p><ins>
<i>&mdash;end example</i>]
</ins></p>

</blockquote>
-->



<h2 id="8.0">Acknowledgements</h2>
<p>
Thanks to Sean Baxter for providing extensive feedback and an early
implementation, which made testing the code samples possible.  Further thanks
to all my reviewers, especially Mungo Gill, Pablo Halpern, and John Lakos who
greatly improved the clarity of this paper, along with fixing many simple typos
and grammatical mistakes.
</p>



<h2 id="9.0">References</h2>
<ul>
  <li>
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0339r6">P0339R6</a>
<code>polymorphic_allocator&lt;&gt;</code> as a vocabulary type,
Pablo Halpern, Dietmar Kühl
  </li>
</ul>

</body>
</html>
