<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Issue 2461: Interaction between allocators and container exception safety guarantees</title>
<meta property="og:title" content="Issue 2461: Interaction between allocators and container exception safety guarantees">
<meta property="og:description" content="C++ library issue. Status: New">
<meta property="og:url" content="https://cplusplus.github.io/LWG/issue2461.html">
<meta property="og:type" content="website">
<meta property="og:image" content="http://cplusplus.github.io/LWG/images/cpp_logo.png">
<meta property="og:image:alt" content="C++ logo">
<style>
  p {text-align:justify}
  li {text-align:justify}
  pre code.backtick::before { content: "`" }
  pre code.backtick::after { content: "`" }
  blockquote.note
  {
    background-color:#E0E0E0;
    padding-left: 15px;
    padding-right: 15px;
    padding-top: 1px;
    padding-bottom: 1px;
  }
  ins {background-color:#A0FFA0}
  del {background-color:#FFA0A0}
  table.issues-index { border: 1px solid; border-collapse: collapse; }
  table.issues-index th { text-align: center; padding: 4px; border: 1px solid; }
  table.issues-index td { padding: 4px; border: 1px solid; }
  table.issues-index td:nth-child(1) { text-align: right; }
  table.issues-index td:nth-child(2) { text-align: left; }
  table.issues-index td:nth-child(3) { text-align: left; }
  table.issues-index td:nth-child(4) { text-align: left; }
  table.issues-index td:nth-child(5) { text-align: center; }
  table.issues-index td:nth-child(6) { text-align: center; }
  table.issues-index td:nth-child(7) { text-align: left; }
  table.issues-index td:nth-child(5) span.no-pr { color: red; }
  @media (prefers-color-scheme: dark) {
     html {
        color: #ddd;
        background-color: black;
     }
     ins {
        background-color: #225522
     }
     del {
        background-color: #662222
     }
     a {
        color: #6af
     }
     a:visited {
        color: #6af
     }
     blockquote.note
     {
        background-color: rgba(255, 255, 255, .10)
     }
  }
</style>
</head>
<body>
<hr>
<p><em>This page is a snapshot from the LWG issues list, see the <a href="lwg-active.html">Library Active Issues List</a> for more information and the meaning of <a href="lwg-active.html#New">New</a> status.</em></p>
<h3 id="2461"><a href="lwg-active.html#2461">2461</a>. Interaction between allocators and container exception safety guarantees</h3>
<p><b>Section:</b> 16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a>, 23.3.13.3 <a href="https://wg21.link/vector.capacity">[vector.capacity]</a>, 23.3.13.5 <a href="https://wg21.link/vector.modifiers">[vector.modifiers]</a> <b>Status:</b> <a href="lwg-active.html#New">New</a>
 <b>Submitter:</b> dyp <b>Opened:</b> 2014-12-06 <b>Last modified:</b> 2015-06-10</p>
<p><b>Priority: </b>3
</p>
<p><b>View other</b> <a href="lwg-index-open.html#allocator.requirements">active issues</a> in [allocator.requirements].</p>
<p><b>View all other</b> <a href="lwg-index.html#allocator.requirements">issues</a> in [allocator.requirements].</p>
<p><b>View all issues with</b> <a href="lwg-status.html#New">New</a> status.</p>
<p><b>Discussion:</b></p>
<p>
When resizing a <code>vector</code>, the accessibility and exception specification of the value type's 
constructors determines whether the elements are copied or moved to the new buffer.
However, the copy/move is performed via the allocator's <code>construct</code> member function, which is 
assumed, but not required, to call the copy/move constructor and propagate only exceptions 
from the value type's copy/move constructor. The issue might also affect other classes. 
<p/>
The current wording in N4296 relevant here is from Table 28 &mdash; "Allocator requirements" in 
16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a>: 
</p>
<blockquote>
<table border="1">
<caption>Table 28 &mdash; Allocator requirements</caption>
<tr>
<th>Expression</th>
<th>Return type</th>
<th>Assertion&#47;note<br/>pre-&#47;post-condition</th>
<th>Default</th>
</tr>

<tr>
<td colspan="4" align="center">
<code>&hellip;</code>
</td>
</tr>

<tr>
<td>
<code>a.construct(c, args)</code>
</td>
<td>
(not used)
</td>
<td>
<i>Effect</i>: Constructs an object of type <code>C</code> at <code>c</code>
</td>
<td>
<code>::new ((void*)c) C(forward&lt;Args&gt;(args)...)</code>
</td>
</tr>

<tr>
<td colspan="4" align="center">
<code>&hellip;</code>
</td>
</tr>

</table>
</blockquote>
<p>
and from 16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a> p9:
</p>
<blockquote><p>
An allocator may constrain the types on which it can be instantiated and the arguments for which its
<code>construct</code> member may be called. If a type cannot be used with a particular allocator, the allocator class
or the call to <code>construct</code> may fail to instantiate.
</p></blockquote>
<p>
I conclude the following from the wording:
</p>
<ol>
<li><p>The allocator is not required to call the copy constructor if the
arguments (args) is a single (potentially const) lvalue of the value
type. Similarly for a non-const rvalue + move constructor. See also
23.2.2 <a href="https://wg21.link/container.requirements.general">[container.requirements.general]</a> p15 which seems to try to require
this, but is not sufficient:
That paragraph specifies the semantics of the allocator's operations,
but not which constructors of the value type are used, if any.
</p></li>
<li>
<p>The allocator may throw exceptions in addition to the exceptions propagated by
the constructors of the value type; it can also propagate exceptions from constructors
other than a copy/move constructor.
</p>
</li>
</ol>
<p>
This leads to an issue with the wording of the exception safety guarantees for vector modifiers in
23.3.13.5 <a href="https://wg21.link/vector.modifiers">[vector.modifiers]</a> p1:
</p>
<blockquote>
<p>
[&hellip;]
</p>
<pre>
void push_back(const T&amp; x);
void push_back(T&amp;&amp; x);
</pre>
<blockquote>
<p>
<i>Remarks</i>: Causes reallocation if the new size is greater than the old capacity. If no 
reallocation happens, all the iterators and references before the insertion point remain valid. 
If an exception is thrown other than by the copy constructor, move constructor, assignment 
operator, or move assignment operator of <code>T</code> or by any InputIterator operation there are 
no effects.
<span  style="color:#C80000;font-weight:bold">
If an exception is thrown while inserting a single element at the end and <code>T</code> 
is <code>CopyInsertable</code> or <code>is_nothrow_move_constructible&lt;T&gt;::value</code>
is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a
non-<code>CopyInsertable</code> <code>T</code>, the effects are unspecified.
</span>
</p>
</blockquote>
</blockquote>
<p>
The wording leads to the following problem:
Copy and move assignment are invoked directly from <code>vector</code>.
For intermediary objects (see <a href="lwg-defects.html#2164" title="What are the semantics of vector.emplace(vector.begin(), vector.back())? (Status: C++20)">2164</a><sup><a href="https://cplusplus.github.io/LWG/issue2164" title="Latest snapshot">(i)</a></sup>),
<code>vector</code> also directly invokes the copy and move constructor of the value type.
However, construction of the actual element within the buffer is invoked via the allocator abstraction.
As discussed above, the allocator currently is not required to call a copy/move constructor.
If <code>is_nothrow_move_constructible&lt;T&gt;::value</code> is <code>true</code> for some value type <code>T</code>,
but the allocator uses modifying operations for <code>MoveInsertion</code> that do throw,
the implementation is required to ensure that "there are no effects",
even if the source buffer has been modified.
<p/>
Similarly, the <code>vector</code> capacity functions specify exception safety guarantees
referring to the move constructor of the value type. For example, <code>vector::resize</code> in 23.3.13.3 <a href="https://wg21.link/vector.capacity">[vector.capacity]</a> p14:
</p>
<blockquote>
<i>Remarks</i>: If an exception is thrown other than by the move constructor of a
non-<code>CopyInsertable</code> <code>T</code> there are no effects.
</blockquote>
<p>
The wording leads to the same issue as described above.
<p/>
Code example:
</p>
<blockquote>
<pre>
template&lt;class T&gt;
class allocator;

class pot_reg_type // a type which creates
                   // potentially registered instances
{
private:
  friend class allocator&lt;pot_reg_type&gt;;
  struct register_t {};

  static std::set&lt;pot_reg_type*&gt;&amp; get_registry()
  {
    static std::set&lt;pot_reg_type*&gt; registry;
    return registry;
  }
  void enregister() noexcept(false)
  {
    get_registry().insert(this);
  }
  void deregister()
  {
    get_registry().erase(this);
  }

public:
  pot_reg_type(void               ) noexcept(true) {}
  pot_reg_type(pot_reg_type const&amp;) noexcept(true) {}
  pot_reg_type(pot_reg_type&amp;&amp;     ) noexcept(true) {}

private:
  pot_reg_type(register_t                     ) noexcept(false)
  { enregister(); }
  pot_reg_type(register_t, pot_reg_type const&amp;) noexcept(false)
  { enregister(); }
  pot_reg_type(register_t, pot_reg_type&amp;&amp;     ) noexcept(false)
  { enregister(); }
};

template&lt;class T&gt;
class allocator
{
public:
  using value_type = T;

  value_type* allocate(std::size_t p)
  { return (value_type*) ::operator new(p); }

  void deallocate(value_type* p, std::size_t)
  { ::operator delete(p); }

  void construct(pot_reg_type* pos)
  {
    new((void*)pos) pot_reg_type((pot_reg_type::register_t()));
  }
  void construct(pot_reg_type* pos, pot_reg_type const&amp; source)
  {
    new((void*)pos) pot_reg_type(pot_reg_type::register_t(), source);
  }

  template&lt;class... Args&gt;
  void construct(T* p, Args&amp;&amp;... args)
  {
    new((void*)p) T(std::forward&lt;Args&gt;(args)...);
  }
}; 
</pre>
</blockquote>
<p>
The <code>construct</code> member function template is only required for rebinding,
which can be required e.g. to store additional debug information in
the allocated memory (e.g. VS2013).
<p/>
Even though the value type has an accessible and <code>noexcept(true)</code> move
constructor, this allocator won't call that constructor for rvalue arguments.
In any case, it does not call a constructor for which vector has formulated its 
requirements. An exception thrown by a constructor called by this allocator is not
covered by the specification in 23.3.13.5 <a href="https://wg21.link/vector.modifiers">[vector.modifiers]</a> and therefore is
guaranteed not to have any effect on the vector object when resizing.
<p/>
For an example how this might invalidate the exception safety
guarantees, see <a href="https://groups.google.com/a/isocpp.org/d/topic/std-discussion/BcM7ya8JeqY/discussion">this post on the std-discussion mailing list</a>.
<p/>
Another problem arises for value types whose constructors are private,
but may be called by the allocator e.g. via friendship.
Those value types are not <code>MoveConstructible</code> 
(<code>is_move_constructible</code> is false), yet they can be <code>MoveInsertable</code>.
It is not possible for <code>vector</code> to create intermediary objects (see <a href="lwg-defects.html#2164" title="What are the semantics of vector.emplace(vector.begin(), vector.back())? (Status: C++20)">2164</a><sup><a href="https://cplusplus.github.io/LWG/issue2164" title="Latest snapshot">(i)</a></sup>) of such a type
by directly using the move constructor.
Current implementations of the single-element forms of <code>vector::insert</code> and <code>vector::emplace</code>
do create intermediary objects by directly calling one of the value type's constructors,
probably to allow inserting objects from references that alias other elements of the container.
As far as I can see, Table 100 &mdash; "Sequence container requirements" in 23.2.4 <a href="https://wg21.link/sequence.reqmts">[sequence.reqmts]</a>
does not require that the creation of such intermediare objects can be performed
by containers using the value type's constructor directly.
It is unclear to me if the allocator's construct function could be used to create those
intermediary objects, given that they have not been allocated by the allocator.
<p/>
Two possible solutions:
</p>
<ol>
<li><p>
Add the following requirement to the <code>allocator_traits::construct</code> function:
If the parameter pack <code>args</code> consists of a single parameter of the type
<code>value_type&amp;&amp;</code>,
the function may only propagate exceptions if <code>is_nothrow_move_constructible&lt;value_type&gt;::value</code>
is <code>false</code>.
<p/>
Requiring <code>alloctor_traits::construct</code> to call a true copy/move constructor
of the value type breaks <code>std::scoped_allocator_adapter</code>,
as pointed out by <a href="https://groups.google.com/a/isocpp.org/d/msg/std-discussion/0yxikZInp-E/Lxj-msFT22cJ">Casey Carter in a post on the std-discussion mailing list</a>.
</p></li>
<li>
<p>
Change vector's criterion whether to move or copy when resizing:
<p/>
Instead of testing the value type's constructors via
<code>is_move_constructible</code>, check the value of
<code>noexcept( allocator_traits&lt;Allocator&gt;::construct(alloc, ptr, rval) )</code>
where
<code>alloc</code> is an lvalue of type <code>Allocator</code>,
<code>ptr</code> is an expression of type <code>allocator_traits&lt;Allocator&gt;::pointer</code>
and
<code>rval</code> is a non-const rvalue of type <code>value_type</code>.
</p>
</li>
</ol>
<p>
A short discussion of the two solutions:
<p/>
Solution 1 allows keeping <code>is_nothrow_move_constructible&lt;value_type&gt;</code>
as the criterion for <code>vector</code> to decide between copying and moving when resizing.
It restricts what can be done inside the <code>construct</code> member function of allocators,
and requires implementers of allocators to pay attention to the value types used.
One could conceive allocators checking the following with a <code>static_assert</code>:
If the value type <code>is_nothrow_move_constructible</code>,
then the constructor actually called for <code>MoveInsertion</code> within the <code>construct</code>
member function is also declared as noexcept.
<p/>
Solution 2 requires changing both the implementation of the default
allocator (add a conditional <code>noexcept</code>) and <code>vector</code> (replace
<code>is_move_constructible</code> with an allocator-targeted check).
It does not impose additional restrictions on the allocator (other than
23.2.2 <a href="https://wg21.link/container.requirements.general">[container.requirements.general]</a> p15),
and works nicely even if the move constructor of a <code>MoveInsertable</code> type is private or deleted
(the allocator might be a friend of the value type).
<p/>
In both cases, an addition might be required to provide the basic exception safety guarantee.
A short discussion on this topic can be found
<a href="https://groups.google.com/a/isocpp.org/d/topic/std-discussion/yZLnYy_y2z0/discussion">
in the std-discussion mailing list</a>.
Essentially, if <code>allocator_traits&lt;Allocator&gt;::construct</code> throws an exception,
the object may or may not have been constructed.
Two solutions are mentioned in that discussion:
</p>
<ol>
<li><p>
<code>allocator_traits&lt;Allocator&gt;::construct</code> needs to tell its caller
whether or not the construction was successful, in case of an exception.
</p></li>
<li><p>
If <code>allocator_traits&lt;Allocator&gt;::construct</code> propagates an exception,
it shall either not have constructed an object at the specified location,
or that object shall have been destroyed
(or it shall ensure otherwise that no resources are leaked).
</p></li>
</ol>

<p><i>[2015-05-23, Tomasz Kami&nacute;ski comments]</i></p>

<p>
Solution 1 discussed in this issue also breaks support for the <code>polymorphic_allocator</code> proposed in the part 
of the Library Fundamentals TS v1, in addition to already mentioned <code>std::scoped_allocator_adapter</code>. Furthermore 
there is unknown impact on the other user-defined state-full allocators code written in the C++11.
<p/>
In addition the library resolution proposed in the LWG issues <a href="lwg-defects.html#2089" title="std::allocator::construct should use uniform initialization (Status: Resolved)">2089</a><sup><a href="https://cplusplus.github.io/LWG/issue2089" title="Latest snapshot">(i)</a></sup> and 
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4462.html">N4462</a>, 
will break the relation between the <code>std::allocator_trait::construct</code> method and 
copy/move constructor even for the standard <code>std::allocator</code>. As example please consider following class:
</p>
<blockquote><pre>
struct NonCopyable
{
  NonCopyable() = default;
  NonCopyable(NonCopyable const&amp;) = delete;
  NonCopyable(NonCopyable&amp;&amp;) = delete;
};

struct InitListConstructor : NonCopyable
{
  InitListConstructor() = default;
  InitListConstructor(std::initializer_list&lt;int&gt;);
  operator int() const;
};
</pre></blockquote>
<p>
For the above declarations following expression are ill-formed:
</p>
<blockquote><pre>
InitListConstructor copy(std::declval&lt;InitListConstructor const&amp;&gt;());
InitListConstructor move(std::declval&lt;InitListConstructor&amp;&amp;&gt;());
</pre></blockquote>
<p>
So the class is not <code>CopyConstructible</code> nor <code>MoveConstructible</code>. However the following are well formed:
</p>
<blockquote><pre>
InitListConstructor copy{std::declval&lt;InitListConstructor const&amp;&gt;()};
InitListConstructor move{std::declval&lt;InitListConstructor&amp;&amp;&gt;()};
</pre></blockquote>
<p>
And will be used by <code>std::allocator&lt;InitListConstructor&gt;::construct</code> in case of move-insertion 
and copy-insertion, after appliance of the resolution proposed in mentioned papers:
</p>
<blockquote>
<p>
The gist of the proposed library fix is simple:
</p>
<ul>
<li><p>if <code>is_constructible_v&lt;TargetType, Args...&gt;</code>, use direct-nonlist-initialization</p></li>
<li><p>otherwise, use brace-initialization.</p></li>
</ul>
</blockquote>
<p>
As consequence the requirement proposed in the Solution 1:
</p>
<blockquote><p>
If the parameter pack <code>args</code> consists of a single parameter of the type <code>value_type&amp;&amp;</code>, 
the function may only propagate exceptions if <code>is_nothrow_move_constructible&lt;value_type&gt;::value</code> is false. 
</p></blockquote>
<p>
Will no longer hold for the <code>std::allocator</code>.
</p>



<p id="res-2461"><b>Proposed resolution:</b></p>





</body>
</html>
