<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Issue 3267: Rebound allocators and is_always_equal</title>
<meta property="og:title" content="Issue 3267: Rebound allocators and is_always_equal">
<meta property="og:description" content="C++ library issue. Status: New">
<meta property="og:url" content="https://cplusplus.github.io/LWG/issue3267.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="3267"><a href="lwg-active.html#3267">3267</a>. Rebound allocators and <code>is_always_equal</code></h3>
<p><b>Section:</b> 16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a> <b>Status:</b> <a href="lwg-active.html#New">New</a>
 <b>Submitter:</b> FrankHB1989 <b>Opened:</b> 2019-08-27 <b>Last modified:</b> 2023-01-14</p>
<p><b>Priority: </b>4
</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>
[allocator.requirements] does not mention the interaction between <code>is_always_equal</code> 
and allocator rebinding. As the result, a rebound allocator may have different 
<code>is_always_equal::value</code> to the original allocator.
<p/>
Further, for an allocator type <code>X</code> satisfying 
<code>std::allocator_type&lt;X&gt;::is_always_equal::value == true</code>, rebound allocators 
of <code>X</code> with same type are not guaranteed equal.
<p/>
Consider:
</p>
<ol>
<li><p><code>X</code> is used as an allocator for <code>value_type</code> used in a node-based container;</p></li>
<li><p><code>Y</code> is the rebound allocator type for the node type used in the implementation;</p></li>
<li><p><code>b1</code> and <code>b2</code> are values of <code>Y</code> from different allocator objects.</p></li>
</ol>
<p>
Then, <code>std::allocator_type&lt;X&gt;::is_always_equal::value == true</code> does not necessarily 
imply <code>b1 == b2</code>.
<p/>
Since some of containers in the standard have already explicitly relied on <code>is_always_equal</code> 
of allocators for their <code>value_type</code> (notably, in the exception specification of the move 
assignment), this can cause subtle problems.
<p/>
In general, the implementation of the move assignment operator of such a container can not avoid 
allocation for new nodes when <code>!std::allocator_traits&lt;Y&gt;::propagate_on_container_move_assignment::value 
&amp;&amp; b1 != b2</code>. This can throw, and it can clash with the required exception specification 
based on <code>std::allocator_traits&lt;value_type&gt;::is_always_equal</code>:
</p>
<blockquote><pre>
#include &lt;utility&gt;
#include &lt;memory&gt;
#include &lt;new&gt;
#include &lt;map&gt;
#include &lt;functional&gt; 
#include &lt;type_traits&gt; 

using K = int;
using V = int;
using P = std::pair&lt;const K, V&gt;; 

bool stop_alloc; 

template&lt;typename T&gt;
struct AT
{
  using value_type = T; 

  std::shared_ptr&lt;void&gt; sp = {}; 

  template&lt;typename U&gt;
  struct rebind
  {
    using other = AT&lt;U&gt;;
  }; 

  using is_always_equal = std::is_same&lt;T, P&gt;; 

  AT() : sp(is_always_equal::value ? nullptr : new T*()) {}

  AT(const AT&amp; a) = default;

  template&lt;typename U&gt;
  AT(const AT&lt;U>&amp; a) noexcept : sp(a.sp) {} 

  T* allocate(std::size_t size)
  {
    if (stop_alloc)
      throw std::bad_alloc();
    return static_cast&lt;T*&gt;(::operator new(size * sizeof(T)));
  } 

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

  friend bool operator==(const AT&amp; x, const AT&amp; y) noexcept
  {
    return !x.sp.owner_before(y.sp) &amp;&amp; !y.sp.owner_before(x.sp);
  } 

  friend bool operator!=(const AT&amp; x, const AT&amp; y) noexcept 
  {
    return !(x == y);
  }

};

using A = AT&lt;P&gt;; 

int main()
{
  // Some sanity checks:
  static_assert(std::is_same_v&lt;A::template rebind&lt;A::value_type&gt;::other, A&gt;);
  // For any U:
  using U = int;
  static_assert(std::is_same_v&lt;A::template rebind&lt;U&gt;::other::template rebind&lt;A::value_type&gt;::other, A&gt;); 

  using C = std::less&lt;&gt;;
  using M = std::map&lt;K, V, C, A&gt;; 

  // As required by the current wording of the container move operator:
  using always_equal = std::allocator_traits&lt;A&gt;::is_always_equal;
  constexpr bool std_nothrow = always_equal::value &amp;&amp; std::is_nothrow_move_assignable_v&lt;C&gt;;
  static_assert(std_nothrow);

  // For conforming implementations:
  // static_assert(!(std_nothrow &amp;&amp; !std::is_nothrow_move_assignable&lt;M&gt;::value)); 

  M m{{K(), V()}}, m2;
  auto a = m.get_allocator(); 

  a.sp = std::make_shared&lt;int&gt;(42);
  stop_alloc = true;

  try
  {
    // Call terminate with conforming implementations. This does not work on libstdc++.
    m2 = std::move(m);
    // For libstdc++, terminate on allocator-extended move constructor call.
    //    M m3(std::move(m), a);
  }
  catch(...)
  {}
}
</pre></blockquote>

<p><i>[2019-10 Priority set to 4 after reflector discussion]</i></p>


<p>
<strong>Previous resolution [SUPERSEDED]:</strong>
</p>
<blockquote class="note">
<p>This wording is relative to <a href="https://wg21.link/N4830" title=" Committee Draft, Standard for Programming Language C++">N4830</a>.</p>

<blockquote class="note">
<p>
[<i>Drafting note:</i> Additional questions: Is it necessary to ensure that<br/>
<code>XX::propagate_on_container_copy_assignment::value == YY::propagate_on_container_copy_assignment::value</code>
is <code>true</code> as well?]
</p>
</blockquote>

<ol>
<li><p>Modify 16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a>, Table [tab:cpp17.allocator] 
"<code><i>Cpp17Allocator</i></code> requirements" as indicated:</p>
<blockquote>
<table border="1">
<caption>Table 34 &mdash; <code><i>Cpp17Allocator</i></code> requirements [tab:cpp17.allocator]</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>typename<br/>
X::template<br/>
rebind&lt;U&gt;::other</code>
</td>
<td>
<code>Y</code>
</td>
<td>
For all <code>U</code> (including <code>T</code>),<br/>
<code>Y::template<br/>
rebind&lt;T&gt;::other</code> is <code>X</code>.<br/>
<ins><code>XX::is_always_equal::value == YY::is_always_equal::value</code><br/>
is <code>true</code>.</ins>
</td>
<td>
See Note A,<br/>
below.
</td>
</tr>

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

</table>
</blockquote>
</li>
</ol>
</blockquote>

<p><i>[2022-04-24; Daniel rebases wording on <a href="https://wg21.link/N4910" title=" Working Draft, Standard for Programming Language C++">N4910</a>]</i></p>


<p>
<strong>Previous resolution [SUPERSEDED]:</strong>
</p>
<blockquote class="note">
<p>
This wording is relative to <a href="https://wg21.link/N4910" title=" Working Draft, Standard for Programming Language C++">N4910</a>.
</p>

<blockquote class="note">
<p>
[<i>Drafting note:</i> Additional questions: Is it necessary to ensure that<br/>
<code>XX::propagate_on_container_copy_assignment::value == YY::propagate_on_container_copy_assignment::value</code>
is <code>true</code> as well?]
</p>
</blockquote>

<ol>
<li><p>Modify 16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a> as indicated:</p>
<blockquote>
<pre>
typename X::template rebind&lt;U&gt;::other
</pre>
<blockquote>
<p>
-16- <i>Result:</i> <code>Y</code>
<p/>
-17- <i>Postconditions:</i> For all <code>U</code> (including <code>T</code>), <code>Y::template rebind&lt;T&gt;::other</code> 
is <code>X</code>. <ins><code>XX::is_always_equal::value == YY::is_always_equal::value</code> is <code>true</code>.</ins>
<p/>
-18- <i>Remarks:</i> If <code>Allocator</code> is a class template instantiation of the form 
<code>SomeAllocator&lt;T, Args&gt;</code>, where <code>Args</code> is zero or more type arguments, and 
<code>Allocator</code> does not supply a <code>rebind</code> member template, the standard <code>allocator_traits</code> 
template uses <code>SomeAllocator&lt;U, Args&gt;</code> in place of <code>Allocator::rebind&lt;U&gt;::other</code> 
by default. For allocator types that are not template instantiations of the above form, no default is provided.
<p/>
-19- [<i>Note 1</i>: The member class template <code>rebind</code> of <code>X</code> is effectively a typedef template. 
In general, if the name <code>Allocator</code> is bound to <code>SomeAllocator&lt;T&gt;</code>, then 
<code>Allocator::rebind&lt;U&gt;::other</code> is the same type as <code>SomeAllocator&lt;U&gt;</code>, where 
<code>SomeAllocator&lt;T&gt;::value_type</code> is <code>T</code> and <code>SomeAllocator&lt;U&gt;::value_type</code> 
is <code>U</code>. &mdash; <i>end note</i>]
</p>
</blockquote>
</blockquote>
</li>
</ol>
</blockquote>

<p><i>[2023-01-08; Jiang An comments and provides improved wording]</i></p>

<p>
Exception specifications of some container operations (added by <a href="https://wg21.link/N4258" title=" Cleaning up noexcept in the Library (Rev 3)">N4258</a> and LWG <a href="lwg-defects.html#3778" title="vector&lt;bool&gt; missing exception specifications (Status: C++23)">3778</a><sup><a href="https://cplusplus.github.io/LWG/issue3778" title="Latest snapshot">(i)</a></sup>) 
are specified with the propagation properties of template parameter <code>Allocator</code>. However, for node-based 
containers and <code>std::deque</code> (and common implementations of <code>std::vector&lt;bool, A&gt;</code>), 
rebound allocators are needed to be propagated, and common implementations are currently detecting the propagation 
properties of rebound allocators.
<p/>
I think if the allocator provided as template argument and the rebound have different propagation properties and 
behaves differently on propagation, then it is difficult or impossible for implementations to follow the current 
exception specifications.
</p>


<p id="res-3267"><b>Proposed resolution:</b></p>
<p>
This wording is relative to <a href="https://wg21.link/N4917" title=" Working Draft, Standard for Programming Language C++">N4917</a>.
</p>

<ol>
<li><p>Modify 16.4.4.6 <a href="https://wg21.link/allocator.requirements">[allocator.requirements]</a> as indicated:</p>
<blockquote>
<pre>
typename X::template rebind&lt;U&gt;::other
</pre>
<blockquote>
<p>
-16- <i>Result:</i> <code>Y</code>
<p/>
-17- <i>Postconditions:</i> For all <code>U</code> (including <code>T</code>), <code>Y::template rebind&lt;T&gt;::other</code> 
is <code>X</code>. <ins>All of <code>XX::is_always_equal::value == YY::is_always_equal::value</code>, 
<code>XX::propagate_on_container_copy_assignment::value == YY::propagate_on_container_copy_assignment::value</code>,  
<code>XX::propagate_on_container_move_assignment::value == YY::propagate_on_container_move_assignment::value</code>, and  
<code>XX::propagate_on_container_swap::value == YY::propagate_on_container_swap::value</code> are <code>true</code>.</ins>
<p/>
-18- <i>Remarks:</i> If <code>Allocator</code> is a class template instantiation of the form 
<code>SomeAllocator&lt;T, Args&gt;</code>, where <code>Args</code> is zero or more type arguments, and 
<code>Allocator</code> does not supply a <code>rebind</code> member template, the standard <code>allocator_traits</code> 
template uses <code>SomeAllocator&lt;U, Args&gt;</code> in place of <code>Allocator::rebind&lt;U&gt;::other</code> 
by default. For allocator types that are not template instantiations of the above form, no default is provided.
<p/>
-19- [<i>Note 1</i>: The member class template <code>rebind</code> of <code>X</code> is effectively a typedef template. 
In general, if the name <code>Allocator</code> is bound to <code>SomeAllocator&lt;T&gt;</code>, then 
<code>Allocator::rebind&lt;U&gt;::other</code> is the same type as <code>SomeAllocator&lt;U&gt;</code>, where 
<code>SomeAllocator&lt;T&gt;::value_type</code> is <code>T</code> and <code>SomeAllocator&lt;U&gt;::value_type</code> 
is <code>U</code>. &mdash; <i>end note</i>]
</p>
</blockquote>
</blockquote>
</li>
</ol>





</body>
</html>
