<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html><head>

<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">

<style type="text/css">

body { color: #000000; background-color: #FFFFFF; }
del { text-decoration: line-through; color: #8B0040; }
ins { text-decoration: underline; color: #005100; }

p.example { margin-left: 2em; }
pre.example { margin-left: 2em; }
div.example { margin-left: 2em; }

code.extract { background-color: #F5F6A2; }
pre.extract { margin-left: 2em; background-color: #F5F6A2;
  border: 1px solid #E1E28E; }

p.function { }
.attribute { margin-left: 2em; }
.attribute dt { float: left; font-style: italic;
  padding-right: 1ex; }
.attribute dd { margin-left: 0em; }

blockquote.std { color: #000000; background-color: #F1F1F1;
  border: 1px solid #D1D1D1;
  padding-left: 0.5em; padding-right: 0.5em; }
blockquote.stddel { text-decoration: line-through;
  color: #000000; background-color: #FFEBFF;
  border: 1px solid #ECD7EC;
  padding-left: 0.5empadding-right: 0.5em; ; }

blockquote.stdins { text-decoration: underline;
  color: #000000; background-color: #C8FFC8;
  border: 1px solid #B3EBB3; padding: 0.5em; }

table { border: 1px solid black; border-spacing: 0px;
  margin-left: auto; margin-right: auto; }
th { text-align: left; vertical-align: top;
  padding-left: 0.8em; border: none; }
td { text-align: left; vertical-align: top;
  padding-left: 0.8em; border: none; }

</style>

<title>Ambiguity and Insecurities with Three-Way Comparison</title>
</head>
<body>

<table style="margin-right: 0em">
<tbody>
<tr><th>Project:</th><td>ISO JTC1/SC22/WG21: Programming Language C++</td></tr>
<tr><th>Number:</th><td>P1380R1</td></tr>
<tr><th>Date:</th><td>2019-01-20</td></tr>
<tr><th>Audience</th><td>CWG, LWG</td></tr>
<tr><th>Revises:</th><td>P1380R0</td></tr>
<tr><th>Author:</th><td>Lawrence Crowl</td></tr>
<tr><th>Contact</th><td>Lawrence@Crowl.org</td></tr>
</tbody>
</table>

<h1>Ambiguity and Insecurities with Three-Way Comparison</h1>

<p>
Lawrence Crowl, Lawrence@Crowl.org
</p>


<h2>Abstract</h2>

<p>
Three-way comparisons are currently unsound.
There are problems
in the definition of three-way comparison functions
and in the definition of template non-type parameter equivalence.
We propose several small changes to improve the soundness of comparison.
</p>


<h2>Contents</h2>

<p>
<a href="#Introduction">Introduction</a><br>
<a href="#General">General Comparison Functions</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#General.cmp.alg">16.11.4 Comparison algorithms [cmp.alg]</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#General.alg.3way">23.7.11 Three-way comparison algorithms [alg.3way]</a><br>
<a href="#Floating">Floating-Point Comparison Functions</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Floating.cmp.alg">16.11.4 Comparison algorithms [cmp.alg]</a><br>
<a href="#NonType">Non-Type Equivalence and Structural Equality</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#NonType.temp.type">12.5 Type equivalence [temp.type]</a><br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#NonType.class.compare.default">10.10.1 Defaulted comparison operator functions [class.compare.default]</a><br>
<a href="#Revisions">Revisions</a><br>
</p>


<h2><a name="Introduction">Introduction</a></h2>

<p>
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0100r0.html">
P0100R0 Comparison in C++</a>
introduced three-way comparison
with the express purpose of making comparison sound.
Programmers should be sure that their comparison operations
meet the constraints of the algorithms they use.
</p>

<p>
Recent changes to the standard have made comparison less sound.
Much of the problem seems to have arisen
with the 'demotion' of the three-way comparison functions
from specialization points providing the primary comparison mechanism
to adjuncts to the three-way comparison operators.
</p>

<p>
Existing algorithms currently take a boolean comparison function.
For compatibility with existing programs,
these algorithms will continue to accept boolean comparisons.
When we introduce three-way comparison into algorithms,
we can do so in a sound manner without invalidating any existing code.
</p>

<p>
All wording edits are relative to
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4778.pdf">
N4778 Working Draft, Standard for Programming Language C++</a>.
</p>


<h2><a name="General">General Comparison Functions</a></h2>

<p>
Comparison operators can serve two purposes,
the application domain or the C++ standard algorithms.
When these two purposes cannot be simultaneously satisfied,
as with floating-point computation,
the operators should be left to the application domain.
To quote from 
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0100r2.html">
P0100R2 Comparison in C++</a>,
"robust standard algorithms should never use [boolean] comparison operators".
The consequence is that
(a) C++ standard algorithms must accept three-way comparion functions and
(b) those functions should not be 'automatically' defined
in terms of boolean comparsion operators.
However, the current definitions do define those functions
in terms of boolean comparison operators.
The intent of this paper is to change those
definitions to reduce or expose errors.
</p>

<p>
The current definitions of several standard functions
infer a strong ordering from existing
<code>&lt;</code> and <code>==</code> operators.
Unfortunately, doing so has a strong chance of making the program unsound.
For example, see Herb Sutter's <code>CaseInsensitiveString</code>
(<a href="http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0515r0.pdf">
P0515R0 Consistent comparison</a>).
If that class were written with traditional operators,
the existing <code>strong_order</code> function
would silently promote the weak ordering to strong.
A strong ordering on user-defined types
takes special care to ensure substitutability.
As a consequence,
it is easy to write a class that fails to provide strong ordering.
So, we should not infer a strong ordering from non-3way operators.
</p>

<p>
In contrast, user-defined types
are much more likely to provide a weak ordering on existing operators,
if for no other reason than to enable use with the standard algorithms.
So, infering a weak ordering from existing operators is far less problematic.
However, there is still the potential
to strengthen a partial ordering into a weak ordering.
As currently defined, this strengthening causes silent failure.
An exception is preferable to silent failure.
</p>


<h3><a name="General.cmp.alg">16.11.4 Comparison algorithms [cmp.alg]</a></h3>

<p>
Constructing a strong ordering
from the existence of <code>&lt;</code> and <code>==</code>
has a strong chance of making the program unsound.
Furthermore, most algorithms do not need a strong ordering,
so the risk outweighs the reward.
Programmers that need the strong ordering can write it.
</p>

<p>
Remove paragraph 1.4.
</p>

<blockquote class="stddel">
<p>
(1.4) &mdash; Otherwise,
if the expressions <code>a == b</code> and <code>a &lt; b</code>
are each well-formed and convertible to bool,
then
</p>
<ul>
<li>(1.4.1) &mdash; if a == b is true,
returns strong_ordering::equal;</li>
<li>(1.4.2) &mdash; otherwise, if a &lt; b is true,
returns strong_ordering::less;</li>
<li>(1.4.3) &mdash; otherwise,
returns strong_ordering::greater.</li>
</ul>
</blockquote>

<p>
Rather than silently promote partial orders to weak orders,
we propose to detect comparisons that are not weak
and throw an exception.
</p>

<p>
Edit paragraph 2.3 as follows.
</p>

<blockquote class="std">
<p>
(2.3) &mdash; Otherwise,
if the expressions <code>a == b</code> and <code>a &lt; b</code>
are each well-formed and convertible to bool,
then
</p>
<ul>
<li>(2.3.1) &mdash; if <code>a == b</code> is <code>true</code>,
returns <code>weak_ordering::equivalent</code>;</li>
<li>(2.3.2) &mdash; otherwise,
if <code>a &lt; b</code> is <code>true</code>,
returns <code>weak_ordering::less</code>;</li>
<li>(2.3.3) &mdash; otherwise,
<ins>if <code>b &lt; a</code> is <code>true</code>,</ins>
returns <code>weak_ordering::greater</code>;</li>
<li><ins>(2.3.4) &mdash; otherwise, 
throws <code>std::domain_error</code>.</ins></li>
</ul>
</blockquote>


<p>
Similarly, the definition for <code>partial_order</code>
incorrectly strengthens a partial ordering.
</p>

<p>
Edit paragraph 3.3 as follows.
</p>

<blockquote class="std">
<p>
(3.3) &mdash; Otherwise,
if the expressions <code>a == b</code> and <code>a &lt; b</code>
are each well-formed and convertible to bool,
then
</p>
<ul>
<li>(3.3.1) &mdash; if <code>a == b</code> is <code>true</code>,
returns <code>partial_ordering::equivalent</code>;</li>
<li>(3.3.2) &mdash; otherwise,
if <code>a &lt; b</code> is <code>true</code>,
returns <code>partial_ordering::less</code>;</li>
<li>(3.3.3) &mdash; otherwise,
<ins>if <code>b &lt; a</code> is <code>true</code>,</ins>
returns <code>partial_ordering::greater</code>;</li>
<li><ins>(3.3.4) &mdash; otherwise, 
returns <code>partial_ordering::unordered</code>.</ins></li>
</ul>
</blockquote>


<p>
Similarly to <code>strong_order</code>,
<code>strong_equal</code> unsafely strengthens the order of the operators.
</p>

<p>
Delete paragraph (4.3).
</p>

<blockquote class="stddel">
<p>
(4.3) &mdash; Otherwise,
if the expression <code>a == b</code>
is well-formed and convertible to <code>bool</code>, then
</p>
<ul>
<li>(4.3.1) &mdash; if <code>a == b</code> is <code>true</code>,
returns <code>strong_equality::equal</code>;</li>
<li>(4.3.2) &mdash; otherwise,
returns <code>strong_equality::nonequal</code>.</li>
</ul>
</blockquote>


<p>
Paragraph 5 defining <code>weak_order</code>
is probably an acceptable default.
Programmers that use <code>operator ==</code> for other purposes
will need to delete the overload.
</p>


<h3><a name="General.alg.3way">23.7.11 Three-way comparison algorithms [alg.3way]</a></h3>

<p>
As before, inferring a <code>strong_ordering</code>
or a <code>strong_equality</code>
from <code>&lt;</code> and <code>==</code>
is unsound.
Again, a weak ordering, with protection,
is probably acceptable.
</p>

<p>
Edit paragraph 1.2 for <code>compare_3way</code> as follows.
</p>

<blockquote class="std">
<p>
(1.2) &mdash; Otherwise,
if the expressions <code>a == b</code> and <code>a &lt; b</code>
are each well-formed and convertible to <code>bool</code>,
returns <del><code>strong_ordering::equal</code></del>
<ins><code>weak_ordering::equivalent</code></ins>
when <code>a == b</code> is <code>true</code>,
otherwise returns <del><code>strong_ordering::less</code></del>
<ins><code>weak_ordering::less</code></ins>
when <code>a &lt; b</code> is <code>true</code>, and
otherwise returns
<del><code>strong_ordering::greater</code></del>
<ins><code>weak_ordering::greater</code></ins>
<ins>when <code>b &lt; a</code> is <code>true</code>, and
otherwise throws <code>std::domain_error</code></ins>.
</p>
</blockquote>

<blockquote class="std">
<p>
(1.3) &mdash; Otherwise,
if the expression <code>a == b</code>
is well-formed and convertible to <code>bool</code>,
returns <del><code>strong_equality::equal</code></del>
<ins><code>weak_equality::equivalent</code></ins>
when <code>a == b</code> is <code>true</code>,
and otherwise returns <del><code>strong_equality::nonequal</code></del>
<ins><code>weak_equality::nonequivalent</code></ins>.
</p>
</blockquote>

<p>
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1186r0.html">
P1186R0 When do you actually use &lt;=&gt;</a>
proposes to move this function definition
into the definition for <code>operator &lt;=&gt;</code>.
If so done,
the edits here would apply to <code>operator &lt;=&gt;</code>.
</p>


<h2><a name="Floating">Floating-Point Comparison Functions</a></h2>

<p>
The boolean comparison operators for standard floating-point types
have a partial order,
so we have no reason to assume that
non-standard floating-point types have stronger orders.
Despite this, non-standard floating-point types are not explicitly handled.
</p>

<p>
There are reasons to wish for a weak ordering on floating-point types,
particularly when keeping sets of 'normally distinguishable' values.
We provide the means to do so.
</p>


<h3><a name="Floating.cmp.alg">16.11.4 Comparison algorithms [cmp.alg]</a></h3>

<p>
The <code>strong_order</code> function
has a special case for ISO/IEC/IEEE floating point.
However, when <code>is_iec559</code> is <code>false</code>,
there is no special handling of floating point.
The default algorithm may lead to intransitive 'greater' relations.
We propose to explicitly provide for non-standard floating point.
</p>

<p>
Edit paragraph 1.1 as follows.
</p>

<blockquote class="std">
<p>
(1.1) &mdash;
<ins>If <code>is_floating_point&lt;T&gt;::value</code> is is <code>true</code>,
returns a result of type <code>strong_ordering</code>
that is a strong order
and that is consistent with <code>T</code>'s comparison operators.</ins>
If <code>numeric_limits&lt;T&gt;::is_iec559</code>
is <ins>also</ins> <code>true</code>,
<del>returns a result of type <code>strong_ordering</code> that</del>
<ins>that order must be</ins>
consistent with the <code>totalOrder</code> operation
as specified in ISO/IEC/IEEE 60559.
</p>
</blockquote>

<p>
Because standard floating point provides only a partial ordering,
we have a gap in the comparison strengths at weak ordering.
So, a similar special case is needed for a weak ordering.
This special case should be consistent
(<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0100r2.html#Consistency">
P0100R2 Comparion in C++, Consistency Between Relations</a>)
with both <code>strong_order</code>
and the partial order implied by the operators.
</p>

<p>
Add a new subparagraph before (2.1) as follows.
</p>

<blockquote class="stdins">
<p>
(2.05) &mdash;
If <code>is_floating_point&lt;T&gt;::value</code> is <code>true</code>,
returns a result of type <code>weak_ordering</code>
that is a weak order
and is consistent with <code>T</code>'s comparison operators
and with <code>strong_order</code>.
If <code>numeric_limits&lt;T&gt;::is_iec559</code>
is also <code>true</code>,
returns a result of type <code>weak_ordering</code>
that has the following equivalence classes ordered from lesser to greater.
</p>
<ul>
<li>Together, all negative NaN values.</li>
<li>Negative infinity.</li>
<li>Separately, each normal and subnormal negative value.</li>
<li>Together, both zero values.</li>
<li>Separately, each normal and subnormal positive value.</li>
<li>Positive infinity.</li>
<li>Together, all positive NaN values.</li>
</ul>
</blockquote>


<h2><a name="NonType">Non-Type Equivalence and Structural Equality</a></h2>

<p>
The definition of non-type template parameter equivalence
appeals to the &lt;=&gt; operator,
but fails to specify the strength.
Specifying a strong ordering solves the problem.
</p>

<p>
Note that using <code>strong_compare</code> or <code>strong_equal</code>
instead of <code>operator &lt;=&gt;</code>
for non-type template argument equivalence
would enable more types as non-type template parameters,
particularly floating-point types.
We make no proposal here, but merely float the idea.
Doing so requires stable conversion
of decimal literals to floating-point values.
</p>


<h3><a name="NonType.temp.type">12.5 Type equivalence [temp.type]</a></h3>

<p>
Template non-type argument equality is ambiguous;
it fails to specify the strength of the equality.
Since this ambiguity affects the application binary interface,
we should be cautious and require strong equality.
</p>

<p>
Edit paragraph 1.5 as follows.
</p>

<blockquote class="std">
<p>
(1.5) &mdash; their remaining corresponding
non-type <var>template-arguments</var>
have the same type and value
after conversion to the type of the <var>template-parameter</var>,
where they are considered to have the same value
if they compare <del>equal</del>
<ins><code>std::strong_ordering::equal</code></ins>
with <code>operator&lt;=&gt;</code>, and
</p>
</blockquote>

<p>
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1185r0.html">
P1185R0 &lt;=&gt; != ==</a>
proposes a "structural equality operator"
(section 4.1 of the paper, section 10.10.1 of the standard).
With adoption of 
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1185r0.html">
P1185R0</a> or a successor,
the above paragraph should be rewritten using strong structural equality.
</p>

<p>
Edit paragraph 1.5 as follows.
</p>

<blockquote class="std">
<p>
(1.5) &mdash; their remaining corresponding
non-type <var>template-arguments</var>
have the same type and value
after conversion to the type of the <var>template-parameter</var>,
where they are considered to have the same value
if they compare equal
with <ins>a strong structural equality</ins>
<code>operator&lt;=&gt;</code>, and
</p>
</blockquote>


<h3><a name="NonType.class.compare.default">10.10.1 Defaulted comparison operator functions [class.compare.default]</a></h3>

<p>
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1185r0.html">
P1185R0 &lt;=&gt; != ==</a>
proposes a "structural equality operator"
(section 4.1 of the paper, section 10.10.1 of the standard).
This proposal calls out floating-point types as an exception,
rather than calling out the fact that the comparison is not strong. 
This approach means that the standard has a stealth defect
if it ever introduces a new built-in type without strong equality.
</p>

<p>
Edit the proposal in 
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1185r0.html">
P1185R0</a> section 4.1 as follows.
</p>

<blockquote class="std">
<p>
An <code>==</code> (equal to) operator is a structural equality operator if:
</p>
<ul>
<li>it is a built-in candidate ([over.built]) where
<del>neither argument has floating point type</del>
<ins>both arguments have strong equality comparisions</ins>, or</li>
<li>it is an operator for a class type <code>C</code>
that is defined as defaulted in the definition of <code>C</code> and
all <code>==</code> operators it invokes are structural equality operators.</li>
</ul>

<p>
A type <code>T</code> has strong structural equality if,
for a glvalue <code>x</code> of type <code>const T</code>,
<code>x == x</code> is a valid expression of type <code>bool</code> and
invokes a structural equality operator.
</p>
</blockquote>

<h2><a name="Revisions">Revisions</a></h2>

<p>From 
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1380r0.html">
P1380R0</a>:
</p>

<ul>
<li><p>Add contents.</p></li>
<li><p>Simplify the abstract.</p></li>
<li><p>Reorganize the presentation to topics,
rather than sections in the standard.</p></li>
<li><p>Add introductory background material.</p></li>
<li><p>Define orderings for non-IEEE floating point types.</p></li>
<li><p>Improve exposition.</p>
<li><p>Correct bugs.</p>
<li><p>Remove the aside on the confusion of
"explicitly defaulted functions defined as deleted".</p></li>
<li><p>Add a revision section.</p></li>
</ul>

</body></html>
