<html><head><meta charset="UTF-8">
<title>Comparing Containers</title>
  <style type='text/css'>
  body {font-variant-ligatures: none;}
  p {text-align:justify}
  li {text-align:justify}
  blockquote.note, div.note
  {
          background-color:#E0E0E0;
          padding-left: 15px;
          padding-right: 15px;
          padding-top: 1px;
          padding-bottom: 1px;
  }
  p code {color:navy}
  ins p code {color:#00A000}
  p ins code {color:#00A000}
  p del code {color:#A00000}
  ins {color:#00A000}
  del {color:#A00000}
  table#boilerplate { border:0 }
  table#boilerplate td { padding-left: 2em }
  table.bordered, table.bordered th, table.bordered td {
    border: 1px solid;
    text-align: center;
  }
  ins.block {color:#00A000; text-decoration: none}
  del.block {color:#A00000; text-decoration: none}
  #hidedel:checked ~ * del, #hidedel:checked ~ * del * { display:none; visibility:hidden }
  </style>
</head><body>
<table id="boilerplate">
<tr><td>Document number</td><td>P0805R1</td></tr>
<tr><td>Date</td><td>2017-02-10</td></tr>
<tr><td>Project</td><td>Programming Language C++, Library Working Group</td></tr>
<tr><td>Reply-to</td><td>Marshall Clow &lt;mclow.lists&#x40;gmail.com&gt;</td></tr>
</table><hr>
<h1>Comparing Containers</h1>
<h2>History</h2>

<p><a href="href=" title="http://wg21.link/P0805R0">P0805R0</a> was presented to LEWG in Albequerque, and was received favorably. They asked for an R1 that also included the rest of the sequence containers (<code>&lt;deque&gt;</code>, <code>&lt;list&gt;</code>, <code>&lt;forward_list&gt;</code> and <code>&lt;vector&gt;</code>) and forwarded the paper to LWG.</p>

<h2>Rationale</h2>

<p>When I was attempting to implement <a href="href=" title="http://wg21.link/P0122R5">P0122R5</a>, I became frustrated with the inability to compare spans of different lengths, and sometimes spans of the same lengths as well. I started thinking about this in general, and realized that our comparison conventions for containers are limited, but for no good reason.</p>

<p>In C++17, <code>std::optional</code> has a nice set of comparison operators.</p>

<ul>
<li>An <code>optional&lt;T&gt;</code> can be compared to a <code>T</code>.</li>
<li>An <code>optional&lt;T&gt;</code> can be compared to a <code>U</code>.</li>
<li>An <code>optional&lt;T&gt;</code> can be compared to an <code>optional&lt;T&gt;</code>.</li>
<li>An <code>optional&lt;T&gt;</code> can be compared to an <code>optional&lt;U&gt;</code>.</li>
<li>An <code>optional&lt;T&gt;</code> can be compared to a <code>nullopt_t</code>.</li>
</ul>


<p>But an <code>array&lt;int, 5&gt;</code> cannot be compared to an <code>array&lt;int, 6&gt;</code>. Nor can an <code>array&lt;int, 5&gt;</code> cannot be compared to an <code>array&lt;long, 5&gt;</code>.  This seems like an artificial limitation to me - these comparisons "make sense", we just don't implement them.  We <em>can</em> compare a <code>vector&lt;int&gt;</code> that contains five elements to a <code>vector&lt;int&gt;</code> that contains six elements, but not a <code>vector&lt;int&gt;</code> to a <code>vector&lt;long&gt;</code>, no matter what size. And if the allocators are different, that won't work either.</p>

<p>As people write more and more generic code, these arbitrary limitations will cause increasing friction.</p>

<p>In the header <code>&lt;algorithm&gt;</code> we have <code>lexicographical_compare</code>. This is a very general algorithm - it compares two sequences, which may be of differing lengths, and/or different underlying types, and returns whether one is &ldquo;less than&rdquo; the other. But that generality is not reflected in the containers, even though their comparisons are defined in terms of <code>lexicographical_compare</code> (Table 85).</p>

<p>The same logic applies to <code>std::tuple</code>. The comparison operators for <code>tuple</code> require that both tuples be the same size ([tuple.rel]/1) but (surprisingly) not that they contain the same types.</p>

<p>Note: I have not suggested changes to <code>basic_string</code>, because it does not use <code>lexicographical_compare</code>, but delegates to a traits class.</p>

<h2>Suggested changes</h2>

<p>These wording changes are relative to <a href="https://wg21.link/N4713">N4713</a>.</p>

<p>In [tuple.rel]/1, change:</p>

<p><em>Requires:</em> For all <code>i</code>, where <code>0 &lt;= i</code> and <code>i &lt;</code> <ins><code>min(sizeof...(TTypes), sizeof...(UTypes))</code></ins> <del><code>sizeof...(TTypes)</code></del>, <code>get&lt;i&gt;(t) == get&lt;i&gt;(u)</code> is a valid expression returning a type that is convertible to <code>bool</code>.  <del><code>sizeof...(TTypes) == sizeof...(UTypes)</code>.</del></p>

<p>In [tuple.rel]/2, change:</p>

<p><em>Returns:</em> <ins><code>false</code> if <code>sizeof...(TTypes) != sizeof...(UTypes)</code>, otherwise, </ins><code>true</code> if <code>get&lt;i&gt;(t) == get&lt;i&gt;(u)</code> for all <code>i</code>, otherwise <code>false</code>. For any two zero-length tuples <code>e</code> and <code>f</code>, <code>e == f</code> returns <code>true</code>.</p>

<p>In [tuple.rel]/4, change:</p>

<p><em>Requires:</em> For all <code>i</code>, where <code>0 &lt;= i</code> and <ins><code>i &lt; min(sizeof...(TTypes), sizeof...(UTypes))</code></ins> <del><code>i &lt; sizeof...(TTypes)</code></del>, both <code>get&lt;i&gt;(t) &lt; get&lt;i&gt;(u)</code> and <code>get&lt;i&gt;(u) &lt; get&lt;i&gt;(t)</code> are valid expressions returning types that are convertible to <code>bool</code>. <del><code>sizeof...(TTypes) == sizeof...(UTypes)</code>.</del></p>

<p>In [tuple.rel]/5, change:</p>

<p><em>Returns:</em> The result of a lexicographical comparison between <code>t</code> and <code>u</code>. <ins>A zero-length tuple is lexicographically less than any non-zero-length tuple.</ins>
The result is defined as: <code>(bool)(get&lt;0&gt;(t) &lt; get&lt;0&gt;(u)) || (!(bool)(get&lt;0&gt;(u) &lt; get&lt;0&gt;(t)) &amp;&amp; ttail &lt; utail)</code>, where <code>rtail</code> for some tuple <code>r</code> is a tuple containing all but the first element of <code>r</code>. For any two zero-length tuples <code>e</code> and <code>f</code>, <code>e &lt; f</code> returns false.</p>

<p>In [array.syn], add the following functions:</p>

<p><pre><ins class="block"><code>
template &lt;class T1, class T2, size_t N1, size_t N2&gt;
  bool operator==(const array&lt;T1,N1&gt;&amp; x, const array&lt;T2,N2&gt;&amp; y);
template &lt;class T1, class T2, size_t N1, size_t N2&gt;
  bool operator!=(const array&lt;T1,N1&gt;&amp; x, const array&lt;T2,N2&gt;&amp; y);
template &lt;class T1, class T2, size_t N1, size_t N2&gt;
  bool operator&lt;(const array&lt;T1,N1&gt;&amp; x, const array&lt;T2,N2&gt;&amp; y);
template &lt;class T1, class T2, size_t N1, size_t N2&gt;
  bool operator&gt;(const array&lt;T1,N1&gt;&amp; x, const array&lt;T2,N2&gt;&amp; y);
template &lt;class T1, class T2, size_t N1, size_t N2&gt;
  bool operator&lt;=(const array&lt;T1,N1&gt;&amp; x, const array&lt;T2,N2&gt;&amp; y);
template &lt;class T1, class T2, size_t N1, size_t N2&gt;
  bool operator&gt;=(const array&lt;T1,N1&gt;&amp; x, const array&lt;T2,N2&gt;&amp; y);
</pre></code></ins></p>

<p>In [deque.syn], add the following functions:</p>

<p><pre><ins class="block"><code>
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator==(const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt; (const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator!=(const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt; (const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt;=(const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt;=(const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
</pre></code></ins></p>

<p>In [forward_list.syn], add the following functions:</p>

<p><pre><ins class="block"><code>
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator==(const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt; (const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator!=(const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt; (const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt;=(const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt;=(const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
</pre></code></ins></p>

<p>In [list.syn], add the following functions:</p>

<p><pre><ins class="block"><code>
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator==(const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt; (const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator!=(const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt; (const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt;=(const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt;=(const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
</pre></code></ins></p>

<p>In [vector.syn], add the following functions:</p>

<p><pre><ins class="block"><code>
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator==(const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt; (const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator!=(const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt; (const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&gt;=(const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
template&lt;class T1, class T2, class Allocator1, class Allocator2&gt;
  bool operator&lt;=(const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
</pre></code></ins></p>

<h2>Implementation status</h2>

<p>I have implemented the changes for both <code>tuple</code> and <code>array</code>.</p>

<h2>Future Directions</h2>

<ul>
<li>Provide compatibility with <a href="https://wg21.link/P0515">P0515 - Consistent Comparisons</a>.</li>
<li>Research and decide if similar functionality should be provided for the associative containers.  P0809R0 - "Comparing Unordered Containers" (no link yet) may influence this direction.</li>
</ul>

</body></html>
