<h1>Implicit and Explicit Default Comparison Operators</h1>

<pre>
Document number: P0432R1
Date: 2016-09-18
Reply-to: David Stone <david@doublewise.net>
Audience: Evolution Working Group
</pre>

<h2>Changes since P0432R0</h2>

<ul>
<li>Fixed some typos</li>
<li>Operators are only generated if the operators that they forward to are not deleted</li>
<li>User-defined comparison operators only inhibit implicit operator== if both arguments are of that type</li>
<li>Added a possible simplified syntax for defaulting operators, inspired by <a href="https://github.com/tvaneerd/isocpp/blob/master/bravely_default.md">Bravely Default (P0481R0)</a></li>
<li>Added section on ODR use and hiding</li>
<li>Added examples</li>
<li>Deleted open question about the definition of operator&lt;</li>
<li>Added open question about how hiding should work.</li>
</ul>

<h2>Background</h2>

<p>Previous attempts at trying to add defaulted comparison operators to the language have generally been divided into two groups, implicit vs. explicit.</p>

<p>The <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3950.html">explicit proposal (N3950)</a> requires users to opt-in using something like this:</p>

<pre><code>struct S {
    bool operator==(S const &amp;) = default;
    bool operator!=(S const &amp;) = default;
    bool operator&lt;(S const &amp;) = default;
    bool operator&gt;(S const &amp;) = default;
    bool operator&lt;=(S const &amp;) = default;
    bool operator&gt;=(S const &amp;) = default;
};
</code></pre>

<p>For small classes, this is almost as verbose as just defining the operators manually. In practice, users would create something like <code>#define GENERATE_COMPARISON_OPERATORS(type)</code> to somewhat automate this boilerplate.</p>

<p>The <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0221r2.html">implicit proposal (P0221)</a> instead takes the view that the compiler should generate these functions for you by default.</p>

<p>P0221 was rejected in Oulu. The primary concerns were:</p>

<ol>
<li>Many people felt that <code>operator&lt;</code> simply does not make sense for many types, so it should not be generated by default.</li>
<li>There was no way to opt out of the operators if they were unwanted.</li>
</ol>

<p>This proposal tries to follow the lead of P0221 while addressing the concerns that lead to it being voted down. The final result is something of a hybrid between the explicit and implicit proposals.</p>

<p>You may also want to read <a href="http://wiki.edg.com/pub/Wg21kona2015/EvolutionWorkingGroup/On_generating_default_comparisons.pdf">On Generating Default Comparisons</a>, as it contains a discussion of how to support partially-ordered types.</p>

<h2>Definition</h2>

<ul>
<li>Support <code>= default</code> syntax to explicitly force the generation of comparison operators</li>
<li>Support <code>= delete</code> syntax to define them as deleted</li>
<li>All generated comparison operators have their constexpr and noexcept status deduced, just like copy constructors</li>
<li>If <code>a</code> and <code>b</code> are the same type, <code>a == b</code> should be defined as equality-by-subobject if that type supports it</li>
<li>If <code>a</code> and <code>b</code> are the same type, <code>a &lt; b</code> should be defined as less-than-by-subobject if that type supports it</li>
<li>If <code>a == b</code> is defined and not deleted and <code>a != b</code> is not user-defined, <code>a != b</code> should be implicitly generated as <code>!(a == b)</code></li>
<li>If <code>a &lt; b</code> is defined and not deleted and <code>a &gt; b</code> is not user-defined, <code>a &gt; b</code> should be implicitly generated as <code>b &lt; a</code></li>
<li>If <code>a == b</code> and <code>a &lt; b</code> are defined and not deleted and <code>a &lt;= b</code> is not user-defined, <code>a &lt;= b</code> should be implicitly generated as <code>a == b or a &lt; b</code></li>
<li>If <code>a &lt;= b</code> is defined and not deleted and <code>a &gt;= b</code> is not user-defined, <code>a &gt;= b</code> should be implicitly generated as <code>b &lt;= a</code></li>
</ul>

<h3>Equality-by-subobject</h3>

<p>A type supports equality-by-subobject if <code>operator==</code> is requested with <code>= default</code> or if the type meets all of the following requirements:</p>

<ul>
<li>It has no user-defined comparison operators for which both arguments are (possibly cv-qualified and possibly reference-qualified) that type</li>
<li>It does not contain a mutable member</li>
<li>It does not contain a virtual base class</li>
<li>It does not contain a virtual member function</li>
<li>It has a default copy constructor</li>
<li>All base classes and non-static data members support <code>operator==</code></li>
</ul>

<p>If <code>operator==</code> is requested with <code>= default</code> and not all base classes and non-static data members support <code>operator==</code>, the program is ill-formed.</p>

<p>Equality-by-subobject is equivalent to the expression <code>(... and (lhs == rhs)</code>, where <code>lhs</code> and <code>rhs</code> are a variadic pack of references to const to all base classes and non-static data members of the type in the order in which they are defined. [note: This would presumably be worded in the same way as the copy constructor is now, and makes empty classes compare equal]</p>

<h3>Less-than-by-subobject</h3>

<p>A type supports less-than-by-subobject if <code>operator&lt;</code> is requested with <code>= default</code>. If not all base classes and non-static data members support <code>operator&lt;</code> and <code>operator==</code>, the program is ill-formed.</p>

<p>Less-than-by-subobject compares each base or non-static data member with <code>operator==</code>. If that comparison returns false, <code>operator&lt;</code> returns <code>lhs.unequal_member &lt; rhs.unequal_member</code>. If the final member is reached for a comparison, it is compared immediately with <code>operator&lt;</code> rather than first calling <code>operator==</code>. Empty types return false for <code>operator&lt;</code>.</p>

<h3>Defaulting and deleting</h3>

<p>A comparison function can be explicitly defaulted or deleted in one of the following ways</p>

<ul>
<li>As member function:
    struct A {
        bool operator&lt;(A const &amp;) const = default;
    };</li>
<li>As a friend function:
    struct B {
        friend bool operator&lt;(B const &amp;, B const &amp;) = default;
    };</li>
<li>As a simplified declaration friend function:
    struct B {
        operator&lt; = default;
    };</li>
<li>As a non-member, non-friend function:
    struct C {
    };
    bool operator&lt;(C const &amp;, C const &amp;) = default;</li>
</ul>

<p>For the case of the non-member, non-friend function, the <code>= default</code> or <code>= delete</code> declaration must occur in the same namespace as the class.</p>

<h3>ODR use, hiding</h3>

<p>The generated operators are only defined if they are ODR-used.</p>

<p>Using both a generated operator and a user-defined operator in the same program makes the program ill-formed. No diagnostic is required if this occurs in separate translation units.</p>

<p>A user-declared comparison function hides the corresponding implicitly-declared function with a similar signature (if any). Hiding means that if both appear in a lookup set, only the user-declared comparison function is considered in overload resolution. Two operator functions have a similar signature if they have the same name (for instance, <code>operator==</code>) and both arguments are of the same type (ignoring cv-qualifiers and reference-qualifiers).</p>

<h2>Examples</h2>

<h3>Do nothing</h3>

<p>If the user writes</p>

<pre><code>struct A {
};
</code></pre>

<p>The following operators are defined:</p>

<pre><code>constexpr bool operator==(A const &amp;, A const &amp;) noexcept {
    return true;
}
constexpr bool operator!=(A const &amp;, A const &amp;) noexcept {
    return false;
}
</code></pre>

<h3>Error</h3>

<p>If the user writes</p>

<pre><code>struct B {
    A a;
};
bool operator&lt;(B const &amp;, B const &amp;) = default;
</code></pre>

<p>There is a compile-time error for final line: There is no visible <code>operator&lt;</code> for A</p>

<h3>Generate all</h3>

<p>If the user writes</p>

<pre><code>struct C {
};
bool operator&lt;(C const &amp;, C const &amp;) = default;
</code></pre>

<p>The following operators are defined:</p>

<pre><code>constexpr bool operator==(C const &amp;, C const &amp;) noexcept {
    return true;
}
constexpr bool operator!=(C const &amp;, C const &amp;) noexcept {
    return false;
}
constexpr bool operator&lt;(C const &amp;, C const &amp;) noexcept {
    return false;
}
constexpr bool operator&gt;(C const &amp;, C const &amp;) noexcept {
    return false;
}
constexpr bool operator&lt;=(C const &amp;, C const &amp;) noexcept {
    return true;
}
constexpr bool operator&gt;=(C const &amp;, C const &amp;) noexcept {
    return true;
}
</code></pre>

<h3>Delete</h3>

<p>If the user writes</p>

<pre><code>struct D1 : C {
    friend bool operator&lt;(D1 const &amp;, D1 const &amp;) = delete;
};
</code></pre>

<p>The following operators are defined, if C is defined as in "Generate all":</p>

<pre><code>constexpr bool operator==(D1 const &amp; lhs, D1 const &amp; rhs) noexcept {
    return static_cast&lt;C const &amp;&gt;(lhs) == static_cast&lt;C const &amp;&gt;(rhs);
    // equivalent to `return true;`
}
constexpr bool operator!=(D1 const &amp; lhs, D1 const &amp; rhs) noexcept {
    return !(lhs == rhs);
    // equivalent to `return false;`
}
bool operator&lt;(D1 const &amp;, D1 const &amp;) = delete;
</code></pre>

<h3>Inheritance</h3>

<p>If the user writes</p>

<pre><code>struct D2 : C {
};
</code></pre>

<p>The following operators are defined, if C is defined as in "Generate all":</p>

<pre><code>constexpr bool operator==(D2 const &amp; lhs, D2 const &amp; rhs) noexcept {
    return static_cast&lt;C const &amp;&gt;(lhs) == static_cast&lt;C const &amp;&gt;(rhs);
    // equivalent to `return true;`
}
constexpr bool operator!=(D2 const &amp; lhs, D2 const &amp; rhs) noexcept {
    return !(lhs == rhs);
    // equivalent to `return false;`
}
</code></pre>

<h3>Default and delete</h3>

<p>If the user writes</p>

<pre><code>struct E {
    int a;
    int b;
    std::string c;
    bool operator&lt;(E const &amp;) const = default;
    bool operator&lt;=(E const &amp;) const = delete;
};
</code></pre>

<p>The following operators are defined:</p>

<pre><code>inline bool operator==(E const &amp; lhs, E const &amp; rhs) {
    return lhs.a == rhs.a and lhs.b == rhs.b and lhs.c == rhs.c;
}
inline bool operator!=(E const &amp; lhs, E const &amp; rhs) {
    return !(lhs == rhs);
}
bool E::operator&lt;(E const &amp; other) const {
    if (this-&gt;a == other.a) {
        return false;
    }
    if (this-&gt;a &lt; other.a) {
        return true;
    }
    if (this-&gt;b == other.b) {
        return false;
    }
    if (this-&gt;b &lt; other.b) {
        return true;
    }
    return this-&gt;c &lt; other.c;
}
inline bool operator&gt;(E const &amp; lhs, E const &amp; rhs) {
    return rhs &lt; lhs;
}
bool operator&lt;=(E const &amp;, E const &amp;) = delete;
</code></pre>

<h3>Mixed-type</h3>

<p>If the user writes</p>

<pre><code>struct F {
    int a;
};
bool operator==(E const &amp;, F const &amp;) noexcept;
bool operator==(F const &amp;, E const &amp;) noexcept;
bool operator==(E const volatile &amp;, F const volatile &amp;) noexcept;
bool operator==(F const volatile &amp;, E const volatile &amp;) noexcept;
</code></pre>

<p>The following operators are defined, if E is defined as in "Default and delete":</p>

<pre><code>constexpr bool operator==(F const &amp; lhs, F const &amp; rhs) noexcept {
    return lhs.a == rhs.a;
}
constexpr bool operator!=(F const &amp; lhs, F const &amp; rhs) noexcept {
    return !(lhs == rhs);
}
bool operator==(E const &amp;, F const &amp;) noexcept; // User-defined
bool operator==(F const &amp;, E const &amp;) noexcept; // User-defined
bool operator==(E const volatile &amp;, F const volatile &amp;) noexcept;   // User-defined
bool operator==(F const volatile &amp;, E const volatile &amp;) noexcept;   // User-defined
inline bool operator!=(E const &amp; lhs, F const &amp; rhs) noexcept {
    return !(lhs == rhs);
}
inline bool operator!=(F const &amp; lhs, E const &amp; rhs) noexcept {
    return !(lhs == rhs);
}
// The operators that forward to other functions perfectly forward all arguments
inline bool operator!=(E const volatile &amp; lhs, F const volatile &amp; rhs) noexcept {
    return !(lhs == rhs);
}
inline bool operator!=(F const volatile &amp; lhs, E const volatile &amp; rhs) noexcept {
    return !(lhs == rhs);
}
</code></pre>

<h3>Weird return types and parameters</h3>

<p>If the user writes</p>

<pre><code>struct G {
};
int operator==(G const &amp;, G &amp;&amp;);
double const &amp; operator&lt;(G, G &amp;&amp;) noexcept;
</code></pre>

<p>Functions equivalent to the following operators are defined:</p>

<pre><code>int operator==(G const &amp;, G &amp;); // User-defined
inline bool operator!=(G const &amp; lhs, G &amp; rhs) {
    return !(lhs == rhs);
}
double const &amp; operator&lt;(G, G &amp;&amp;) noexcept; // User-defined
double const &amp; operator&gt;(G &amp;&amp; lhs, G const &amp; rhs) noexcept {
    return rhs &lt; std::move(lhs);
}
double const &amp; operator&gt;(G &amp;&amp; lhs, G &amp;&amp; rhs) noexcept {
    return std::move(rhs) &lt; std::move(lhs);
}
</code></pre>

<p>The return type of <code>operator!=</code> is bool because <code>operator!</code> applied to a value of type <code>int</code> returns type <code>bool</code>. In general, the return type of generated functions should be equivalent to <code>decltype(auto)</code>. <code>operator&gt;</code> overloads are only defined for arguments types which are (possibly cv-qualified) G and for which the body is valid. Generated operators always use reference type arguments. <code>operator&lt;=</code> and <code>operator&gt;=</code> is not defined because there is no type which can be passed to both G &amp; and G &amp;&amp;.</p>

<h2>Open questions</h2>

<h3>Qualifiers</h3>

<p>Do we allow virtual, volatile qualifiers, no const qualification, rvalue-references, or pass by value on defaulted functions? Do we allow multiple such declarations and overloading (for instance, a volatile and non-volatile <code>operator==</code>)?</p>

<h3>Hiding</h3>

<p>How should 'hiding' work for this proposal? Hiding is more important for implicitly-generated functions than explicitly-generated functions. An example will describe the issue best:</p>

<pre><code>struct Base {
};
bool operator==(Base const &amp;, Base const &amp;);

struct Derived : Base {
    int member;
};

Derived x;
Derived y;
x == y;
</code></pre>

<p>The current behavior of <code>x == y</code> is to perform a derived-to-base conversion on both <code>x</code> and <code>y</code>, and then pass the result of that to the user-defined <code>bool operator==(Base const &amp;, Base const &amp;)</code>. Without a provision for hiding, this code will change meaning under this proposal to be defined as:</p>

<pre><code>bool operator==(Derived const &amp; lhs, Derived const &amp; rhs) {
    return static_cast&lt;Base const &amp;&gt;(lhs) == static_cast&lt;Base const &amp;&gt;(rhs) and lhs.member == rhs.member;
}
</code></pre>

<p>I believe that implicitly-generated operators should be hidden by user-defined operators, as outlined in P0221. This means that if the expression <code>a op b</code> would have been valid without the implicitly-generated function, it should not change behavior and does not count as an ODR-use of the function.</p>

<p>The main open question here is should all the generated operators be hidden by user-defined operators, or should only implicitly-generated operators be hidden? For instance, what should be the behavior if the user in the above example had written this instead:</p>

<pre><code>struct Base {
};
bool operator==(Base const &amp;, Base const &amp;);

struct Derived : Base {
    int member;
};
bool operator==(Derived const &amp;, Derived const &amp;) = default;

Derived x;
Derived y;
x == y;
</code></pre>

<p>It seems reasonable that because this is the user explicitly expressing their intent that they want the generated <code>operator==</code> they should get it, and therefore the comparison should compare both <code>Base</code> and <code>member</code>. However, it also seems reasonable that a user specifying <code>= default</code> shouldn't generate a different type of thing than would be generated by default. I suspect that the least surprising option for users is that anything explicitly defaulted is not hidden and is treated the same as any other function when it comes to overload resolution.</p>


