<html>
<head>
<title>P0564R0: Wording for three-way comparisons</title>

<style type="text/css">
  ins { text-decoration:none; font-weight:bold; background-color:#A0FFA0 }
  .new { text-decoration:none; font-weight:bold; background-color:#D0FFD0 }
  .optional { color:#606000 }
  del { text-decoration:line-through; background-color:#FFA0A0 }  
  strong { font-weight: inherit; color: #2020ff }
  table, td, th { border: 1px solid black; border-collapse:collapse; padding: 5p
x }
</style>
</head>

<body>
ISO/IEC JTC1 SC22 WG21 P0564R0<br/>
Jens Maurer &lt;Jens.Maurer@gmx.net><br/>
Target audience: EWG<br/>
2017-02-06<br/>

<h1>P0564R0: Wording for three-way comparisons</h1>

<h2>Introduction</h2>

This paper presents the detailed wording changes to implement P0515R0
"Consistent comparison" by Herb Sutter.  Any differences in semantics
to P0515R0 are unintentional.

<h2>Open Issues</h2>

<ul>
<li>The rules in P0515R0 (options 2 or 3) when a compiler-declared
operator&lt;=> is deleted vs. not declared are not yet accurately
reflected.</li>
</ul>

<h2>Wording</h2>

In 2.12 [lex.operators], add <code>&lt;=></code> as an option for the
grammar non-terminal <em>preprocessing-op-or-punc</em>.
<p>
Add a new section 5.9 [expr.spaceship] before the existing 5.9 [expr.rel]:

<blockquote class="new">
<h2>5.9 Three-way comparison operator [expr.spaceship]</h2>

The three-way comparison operator groups left-to-right.

<pre>
<em>compare-expression:
       shift-expression
       compare-expression &lt;=> shift-expression</em>
</pre>

If both operands have (possibly different) floating-point types, the
usual arithmetic conversions are applied to the operands.  The
operator yields a prvalue of type <code>std::partial_ordering</code>.
The expression <code>a &lt;=> b</code>
yields <code>std::partial_ordering::less</code> if a is less than
b, <code>std::partial_ordering::greater</code> if a is greater than b,
<code>std::partial_ordering::equivalent</code> if a is equivalent to b,
and <code>std::partial_ordering::unordered</code> otherwise.
<p>

If both operands have the same enumeration type E: If E has more than
one enumerator with a given value, the operator yields a prvalue of
type <code>std::weak_ordering</code>, otherwise it yields a prvalue of
type <code>std::strong_ordering</code>.  In either case, the operator
yields the result of converting the operands to the underlying type of
E and applying <code>&lt;=></code> to the converted operands.

<p>
If at least one of the operands is a pointer, pointer conversions
(4.11 [conv.ptr]), function pointer conversions (4.13 [conv.fctptr]),
and qualification conversions (4.5 [conv.qual]) are performed on both
operands to bring them to their composite pointer type (Clause 5
[expr]).  If at least one of the operands is a pointer to member,
pointer to member conversions (4.12) and qualification conversions
(4.5) are performed on both operands to bring them to their composite
pointer type (Clause 5).  If both operands are null pointer constants,
but not both of integer type, pointer conversions (4.11 [conv.ptr])
are performed on both operands to bring them to their composite
pointer type (Clause 5 [expr]).  In all cases, after the conversions,
the operands shall have the same type. [ Note: Array-to-pointer
conversions (4.2 [conv.array]) are not applied. -- end note ]
<p>
  
If the composite pointer type is a function pointer type, a
pointer-to-member type, or <code>std::nullptr_t</code>, the operator
yields a prvalue of type <code>std::strong_equality</code>; the
operator yields <code>std::strong_equality::equal</code> if the
(possibly converted) operands compare equal (5.10 [expr.eq])
and <code>std::strong_equality::unequal</code> if they compare
unequal, otherwise the result of the operator is unspecified.
<p>

If the composite pointer type is an object pointer type, the operator
yields the result of converting both operands
to <code>std::uintptr_t</code> and comparing the converted operands
using <code>&lt;=></code>, where the result is consistent with the
result of equality and relational comparisons.  [ Note: That means, if
two pointer operands p and q compare equal (5.10 [expr.eq]), <code>p
&lt;=> q</code> yields <code>std::strong_ordering::equal</code>; if p
and q compare unequal, <code>p &lt;=> q</code>
yields <code>std::strong_ordering::less</code> if q compares greater
than p and <code>std::strong_ordering::greater</code> if p compares
greater than q (5.9 [expr.rel]). -- end note ]
<p>

If both operands have the same integral type, the operator yields a
prvalue of type <code>std::strong_ordering</code>.  The result
is <code>std::strong_ordering::equal</code> if both operands are
arithmetically equal, <code>std::strong_ordering::less</code> if the
first operand is arithmetically less than the second operand,
and <code>std::strong_ordering::greater</code> otherwise. [ Note:
Integral promotions (4.6 [conv.prom]) or integral conversions (4.8
[conv.integral]) are not applied. -- end note ]
<p>
  
Otherwise, the program is ill-formed.

</blockquote>

Change the grammar in 5.9(old) [expr.rel]:

<blockquote>
<pre>
<em>relational-expression:
       <del>shift-expression</del> <ins>compare-expression</ins>
       relational-expression &lt; <del>shift-expression</del> <ins>compare-expression</ins>
       relational-expression > <del>shift-expression</del> <ins>compare-expression</ins>
       relational-expression &lt;= <del>shift-expression</del> <ins>compare-expression</ins>
       relational-expression >= <del>shift-expression</del> <ins>compare-expression</ins></em>
</pre>
</blockquote>

Change in 5.20 [expr.const] paragraph 2:

<blockquote>
  <ul>
    <li>...</li>
    <li><ins>a three-way comparison (5.9(new) [expr.spaceship])
    comparing pointers that do not point to subobjects of the same
    complete object;</ins></li>
    <li>a relational (5.9) or equality (5.10) operator where the
    result is unspecified; or</li>
    <li>...</li>
  </ul>
</blockquote>

Add a new section to clause 12 [special]:

<blockquote class="new">
<h2>12.9 Comparisons [class.compare]</h2>

A defaulted comparison operator function (5.9(new) [expr.spaceship],
5.9 [expr.rel], 5.10 [expr.eq]) for some class C shall be a
non-template function declared in the <em>member-specification</em> of
C that
<ul>
  <li>is a non-static member of C having one parameter of type <code>const C&</code> or</li>
  <li>a static member or friend of C having two parameters of type <code>const C&</code>,</li>
</ul>
in all cases naming the injected-class-name.


<h3>12.9.1 Three-way comparison [class.spaceship]</h3>

For a defaulted three-way comparison operator function, the declared
return type shall be either <code>auto</code>, in which case the
return type is deduced as described below, or one of the category
types, in which case a value of the deduced return type shall be
implicitly convertible to the declared return type.
<p>
The direct base class subobjects of C, in the order of their
declaration in the <em>base-specifier-list</em> of C, followed by the
non-static data members of C, in the order of their declaration in
the <em>member-specification</em> of C, form a list of subobjects.  In
that list, any subobject of array type is recursively expanded to the
sequence of its elements, in the order of increasing subscript.  Let
x<sub>i</sub> denote the i-th element in the expanded list of
subobjects for an object x, where x<sub>i</sub> is an lvalue
if it is has reference type, and a const xvalue otherwise. [ Note:
This yields the same result as a class member access (5.2.5
[class.mem]) on <code>const C</code>. -- end note ] The type of the
expression <code>x<sub>i</sub> &lt=> x<sub>i</sub></code> is denoted
by R<sub>i</sub>. If any R<sub>i</sub> is not a category type, the
return type is <code>void</code> and the operator function is defined
as deleted.
<p>
  
Otherwise, the return type R is deduced as follows:

<ul>
<li>If the list of subobjects is empty, R
is <code>strong_ordering</code>.</li>

<li>Otherwise, if
  <ul><li>at least one R<sub>i</sub> is <code>std::weak_equality</code> or</li>
    <li>at least one R<sub>i</sub>
    is <code>std::strong_equality</code> and at least one
    R<sub>j</sub> is <code>std::partial_ordering</code>
    or <code>std::weak_ordering</code>,</li>
  </ul>
R is <code>std::weak_equality</code>.</li>

<li>Otherwise, if at least one R<sub>i</sub>
is <code>std::strong_equality</code>, R
is <code>std::strong_equality</code>.</li>

<li>Otherwise, if at least one R<sub>i</sub>
is <code>std::partial_ordering</code>, R
is <code>std::partial_ordering</code>.</li>

<li>Otherwise, if at least one R<sub>i</sub>
is <code>std::weak_ordering</code>, R
is <code>std::weak_ordering</code>.</li>

<li>Otherwise, R is <code>std::strong_ordering</code>.</li>
</ul>

The return value V of the three-way comparison operator function
invoked with arguments x and y of the same type is determined by
comparing corresponding elements x<sub>i</sub> and y<sub>i</sub> in
the expanded lists of subobjects for x and y and converting each of the
resulting values to type R.
Let i denote the first index where x<sub>i</sub> &lt;=>
y<sub>i</sub> yields a result value different
from <code>R<sub>i</sub>::equivalent</code>; V is that result
value converted to R.  If no such index exists, V
is <code>std::strong_ordering::equal</code> converted to R.</li>
</ul>
</blockquote>

<em>Editing note: The following implements default generation of operator&lt;=>, thereby also providing all relational and equality operators.</em>

<blockquote class="new">
<div class="optional">
For a class type T, a non-member <code>operator&lt;=></code> is implicitly declared as
<pre>
    friend auto operator<=>(const X&, const X&) noexcept = default;
</pre>
(where X names the injected-class-name) if
<ul>
  <li>there is no declaration of a comparison operator (5.9(new)
  [expr.spaceship], 5.9 [expr.rel], 5.10 [expr.eq]) (including friend
  declarations) in the <em>member-specification</em> of T or a
  public base class of T</li>
  <!-- <li>T is not a closure type (5.1.5 [expr.prim.lambda]),</li> -->
  <li>T has no user-provided or deleted copy constructor (12.8.1
  [class.copy.ctor]),</li>
  <li>the first parameter of all copy constructors of T has
  type <code>const T&</code>,</li>
  <li>T has no user-provided copy assignment operator (12.8.2
  [class.copy.assign]),</li>
  <li>the parameter of all copy assignment operators of T has
  type <code>T</code> or type <code>const T&</code>, and</li>
  <li>T has no user-provided destructor (12.4 [class.dtor]).</li>
</ul>

If the definition of <code>operator&lt;=></code> would satisfy the
requirements of a <code>constexpr</code> function (7.1.5
[dcl.constexpr]), the implicitly-declared <code>operator&lt;=></code>
is <code>constexpr</code>.
</div>
</blockquote>

<blockquote class="new">
<h3>12.9.2 Other comparison operators [class.rel.eq]</h3>

A defaulted relational (5.9 [expr.rel]) or equality (5.10 [expr.eq])
operator function for some operator @ shall have a declared return
type <code>bool</code>.
<p>
The operator function with parameters x and y is defined as deleted if
  <ul>
    <li>overload resolution (13.3), as applied to <code>x &lt;=>
    y</code> (also considering synthesized candidates with reversed
    order of parameters), results in an ambiguity or a function that
    is deleted or inaccessible from the operator function, or</li>
    
    <li>the operator @ cannot be applied to the return type of <code>x
    &lt;=> y</code> or <code>y &lt;=> x</code>.</li>
  </ul>
  
<p>
Otherwise, the operator function yields <code>x &lt;=> y @ 0</code> if
an operator&lt;=> with the original order of parameters was selected,
or <code>0 @ y &lt;=> x</code> otherwise.
<p>

[ Example:
<pre>
  struct C {
    friend std::strong_equality operator&lt;=>(const C&, const C&);
    bool operator==(const C& x, const C& y) = default;  // ok, returns x &lt;=> y == 0
    bool operator&lt;(const C&, const C&) = default;       // ok, function is deleted
  };
</pre>
-- end example ]
</blockquote>

Change in 13.3.1.2 [over.match.oper] paragraph 6 and add a new paragraph after that:

<blockquote>
The set of candidate functions for overload resolution is the union of
the member candidates, the non-member candidates, and the built-in
candidates <ins>, all for operator@</ins>. <ins>If the operator is a
relational (5.9 [exp.rel]) or equality (5.10 [expr.eq]) operator, a
member or non-member candidate <code>operator&lt;=></code>
is added to the set of candidate functions for overload resolution
if <span class="optional">the candidate was user-declared and</span></ins>
<ul>
  <li><ins>the candidate has return type <code>std::strong_ordering</code>, <code>std::weak_ordering</code>, or <code>std::partial_ordering</code> or</ins></li>
  <li><ins>the candidate has return type <code>std::strong_equality</code> or <code>std::weak_equality</code> and @ is == or !=.</ins></li>
</ul>
<ins>For each such added candidate whose parameter types differ, a
synthesized candidate is added to the candidate set where the order of
the two parameters is reversed.</ins>
<p>
  
The argument list contains all of the operands of the
operator. The best function from the set of
candidate functions is selected according to 13.3.2 and 13.3.3. [
Footnote: ... ] [ Example: ... -- end example ]
<p>

<div class="new">
<span class="optional">
If overload resolution yields no viable function (13.3.2
[over.match.viable]) for a relational (5.9 [expr.rel]) or equality
(5.10 [expr.eq]) operator
<!-- and the operands are of the same complete class type (ignoring
cv-qualification)--> , then overload resolution is attempted again,
with implicitly-declared operator&lt;=> functions added to the
candidate set.
</span>
</div>

<p>
  
<ins>If a candidate for <code>operator&lt;=></code> is selected by
overload resolution, but @ is not &lt;=>, the call
to <code>operator@</code> with arguments x and y yields the value
of <code>0 @ operator&lt;=>(y,x)</code> if the selected candidate is a
synthesized candidate with reversed order of parameters, or
<code>operator&lt;=>(x,y) @ 0</code> otherwise.
</ins>
    
<p>
If a built-in candidate is selected by overload resolution, the
operands of class type are converted to the types of the corresponding
parameters of the selected operation function, except that ...

</blockquote>

Add new bullets before bullet 6 in 13.3.3 [over.match.best] paragraph 1:

<blockquote>

<ul>
<li>...</li>
<li><ins>F1 is an operator function for a relational (5.9 [expr.rel]) or
quality (5.10 [expr.eq]) operator and F2 is not [ Example:</ins>
  <pre>
<ins>    struct S {
      auto operator&lt;=>(const S&, const S&) = default;  // #1
      bool operator&lt;(const S&, const S&);              // #2
    };
    bool b = S() &lt; S();   // calls #2</ins>
</pre>
<ins>-- end example ] or, if not that,</ins></li>
<li><ins>F1 and F2 are operator functions for operator&lt;=> and F2 is
a synthesized candidate with reversed order of parameters and F1 is
not [ Example:</ins>
  <pre>
<ins>    struct S {
      std::weak_ordering operator&lt;=>(const S&, int);  // #1
      std::weak_ordering operator&lt;=>(int, const S&);  // #2
    };
    bool b = 1 &lt; S();   // calls #2</ins>
</pre>
<ins>-- end example ] or, if not that,</ins></li>



<li>F1 is generated from a deduction-guide (13.3.1.8) and F2 is not [ Example: ... ]</li>
</ul>
</blockquote>

In 13.5 [over.oper] paragraph 1, add <code>&lt;=></code> as an option
for the grammar non-terminal <em>operator</em>.

<p>

Add two new paragraphs after 13.6 [over.built] paragraphs 12:

<blockquote class="new">
For every integral type T there exist candidate operator functions of the form
<pre>
    std::strong_ordering operator&lt;=>(T , T );
</pre>
<p>
For every pair of floating-point types L and R, there exist candidate
operator functions of the form
<pre>
    std::partial_ordering operator&lt;=>(L , R );
</pre>
</blockquote>

Change in 13.6 [over.built] paragraphs 15 and 16:

<blockquote>
For every T, where T is an enumeration type or a pointer type, there
exist candidate operator functions of the form
<pre>
  bool    operator&lt;(T , T );
  bool    operator>(T , T );
  bool    operator&lt;=(T , T );
  bool    operator>=(T , T );
  bool    operator==(T , T );
  bool    operator!=(T , T );
  <ins>R       operator&lt;=>(T , T );</ins>
</pre>
<ins>where R is the result type specified in 5.9 [expr.spaceship].</ins>
<p>

For every pointer to member type T or type <code>std::nullptr_t</code>
there exist candidate operator functions of the form
<pre>
     bool     operator==(T , T );
     bool     operator!=(T , T );
     <ins>std::strong_equality operator&lt;=>(T , T );</ins>
</pre>
</blockquote>

Add a new paragraph after 13.6 [over.built] paragraphs 16:

<blockquote class="new">
For every array type T there exist candidate operator functions of the form
<pre>
     R     operator&lt;=>(T& , T& );
     R     operator&lt;=>(T&& , T&& );
</pre>
where R is the result type specified in 5.9 [expr.spaceship].
</blockquote>

Add a new paragraph after 15.4 [except.spec] paragraph 10:

<blockquote>
A deallocation function (3.7.4.2) with no explicit noexcept-specifier
has a non-throwing exception specification.
</blockquote>

<blockquote class="new">
The exception specification for <span class="optional">an
implicitly-declared three-way comparison operator, or</span> a
three-way comparison without a <em>noexcept-specifier</em> that is
defaulted on its first declaration, is potentially-throwing if and
only if the invocation of any comparison operator in the implicit
definition is potentially-throwing.
</blockquote>

</body>
</html>

