<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>P0805R2</td></tr>
<tr><td>Date</td><td>2018-06-22</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>

<p>In Rapperswil, LWG asked for a couple of editorial changes to <a href="href=" title="http://wg21.link/P0805R1">P0805R1</a> (reordering template parameters and function lists), and I rebased on N4750. Also, Daniel pointed out that I was relying on Table 77 and 78 for semantics of the comparison operators, and they only define semantics for comparisons between containers of the same type.</p>

<p>For <a href="href=" title="http://wg21.link/P0805R2">P0805R2</a> Daniel, Jonathan and Casey helped me add the appropriate wording for comparing containers of different types.</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/N4750">N4750</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 <del>returning a type </del>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> <code>true</code> if <ins><code>sizeof...(TTypes) == sizeof...(UTypes)</code>and</ins> <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]/5, 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 <del>returning types </del>that are convertible to <code>bool</code>. <del><code>sizeof...(TTypes) == sizeof...(UTypes)</code>.</del></p>

<p>In [tuple.rel]/6, 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 [sequence.reqmts]/3, change:</p>

<p>In Tables 81 and 82, <code>X</code> denotes a sequence container class <ins>containing elements of type <code>T</code>, <code>a</code> denotes a value of type <code>X</code>, <code>X2</code> denotes a different specialization of the same class template as <code>X</code> (if any) containing elements of type <code>T2</code>, <code>a2</code> denotes a value of type <code>X2</code></ins><del>, <code>a</code> denotes a value of type <code>X</code> containing elements of type <code>T</code></del>, <code>u</code> denotes the name of a variable being declared [...]</p>

<p>Append six rows to the end of table 81:
<ins></p>

<table>
<tr><th>Expression</th><th>Return type</th><th>Assertion/note pre-/post-condition</th></tr>
<tr>

<td><ins><code>a == a2</code><ins></td>

<td><ins>convertible to <code>bool</code><ins></td>

<td><ins><i>Returns:</i> <code>equal(a.begin(), a.end(), a2.begin(), a2.end())</code>.  <i>Requires:</i> <code>T</code> and <code>T2</code> model <code>EqualityComparableWith</code>. <i>Complexity:</i> Constant if <code>a.size() != a2.size()</code>, linear otherwise.<ins></td>

</tr><tr>

<td><ins><code>a != a2</code><ins></td>

<td><ins>convertible to <code>bool</code><ins></td>

<td><ins>Equivalent to <code>!(a == a2)</code>.<ins></td>

</tr><tr>

<td><ins><code>a < a2</code><ins></td>

<td><ins>convertible to <code>bool</code><ins></td>

<td><ins><i>Returns:</i> <code>lexicographical_compare(a.begin(), a.end(), a2.begin(), a2.end())</code>. <i>Requires:</i> <code>T</code> and <code>T2</code> model <code>StrictTotallyOrdered</code>. <i>Complexity:</i> linear.<ins></td>

</tr><tr>

<td><ins><code>a > a2</code><ins></td>

<td><ins>convertible to <code>bool</code><ins></td>

<td><ins>Equivalent to <code>a2 < a</code><ins></td>

</tr><tr>

<td><ins><code>a <= a2</code><ins></td>

<td><ins>convertible to <code>bool</code><ins></td>

<td><ins>Equivalent to <code>!(a > a2)</code><ins></td>

</tr><tr>

<td><ins><code>a >= a2</code><ins></td>

<td><ins>convertible to <code>bool</code><ins></td>

<td><ins>Equivalent to <code>!(a < a2)</code><ins></td>

</tr>
</table>


<p></ins></p>

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

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

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

<pre><code><ins class="block">
template&lt;class T1, class Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, class Allocator2&gt;
  bool operator&gt;=(const deque&lt;T1, Allocator1&gt;&amp; x, const deque&lt;T2, Allocator2&gt;&amp; y);
</ins>
</code></pre>

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

<pre><code><ins class="block">
template&lt;class T1, class Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, class Allocator2&gt;
  bool operator&gt;=(const forward_list&lt;T1, Allocator1&gt;&amp; x, const forward_list&lt;T2, Allocator2&gt;&amp; y);
</ins>
</code></pre>

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

<pre><code><ins class="block">
template&lt;class T1, class Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, class Allocator2&gt;
  bool operator&gt;=(const list&lt;T1, Allocator1&gt;&amp; x, const list&lt;T2, Allocator2&gt;&amp; y);
</ins>
</code></pre>

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

<pre><code><ins class="block">
template&lt;class T1, class Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, 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 Allocator1, class T2, class Allocator2&gt;
  bool operator&gt;=(const vector&lt;T1, Allocator1&gt;&amp; x, const vector&lt;T2, Allocator2&gt;&amp; y);
</ins>
</code></pre>

<h2>Implementation status</h2>

<p>This has been implemented (but not shipped) for libc++.</p>

<h2>Future Directions</h2>

<h2>Thanks</h2>

<p>Thanks to Daniel Krügler for his advice and wording assistance. Thanks to Jonathan Wakely abd Casey Carter for their wording assistance.</p>
</body></html>
