<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><=> != ==</title>
<style type="text/css">html {
	position: relative;
	max-width: 1024px;
	height: 100%;
}
body {
	font-family: Helvetica, arial, sans-serif;
	font-size: 14px;
	line-height: 1.6;
	padding-top: 10px;
	padding-bottom: 10px;
	background-color: white;
	padding: 30px;
}
body>*:first-child {
	margin-top: 0 !important;
}
body>*:last-child {
	margin-bottom: 0 !important;
}
a {
	color: #4183C4;
}
a.absent {
	color: #cc0000;
}
a.anchor {
	display: block;
	padding-left: 30px;
	margin-left: -30px;
	cursor: pointer;
	position: absolute;
	top: 0;
	left: 0;
	bottom: 0;
}
h1, h2, h3, h4, h5, h6 {
	margin: 20px 0 10px;
	padding: 0;
	font-weight: bold;
	-webkit-font-smoothing: antialiased;
	cursor: text;
	position: relative;
}
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
	background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
	text-decoration: none;
}
h1 tt, h1 code {
	font-size: inherit;
}
h2 tt, h2 code {
	font-size: inherit;
}
h3 tt, h3 code {
	font-size: inherit;
}
h4 tt, h4 code {
	font-size: inherit;
}
h5 tt, h5 code {
	font-size: inherit;
}
h6 tt, h6 code {
	font-size: inherit;
}
h1 {
	font-size: 28px;
	color: black;
}
h2 {
	font-size: 24px;
	border-bottom: 1px solid #cccccc;
	color: black;
}
h3 {
	font-size: 18px;
}
h4 {
	font-size: 16px;
}
h5 {
	font-size: 14px;
}
h6 {
	color: #777777;
	font-size: 14px;
}
p, blockquote, ol, dl, li, table, pre {
	margin: 15px 0;
}
hr {
	background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
	border: 0 none;
	color: #cccccc;
	height: 4px;
	padding: 0;
}
body>h2:first-child {
	margin-top: 0;
	padding-top: 0;
}
body>h1:first-child {
	margin-top: 0;
	padding-top: 0;
}
body>h1:first-child+h2 {
	margin-top: 0;
	padding-top: 0;
}
body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
	margin-top: 0;
	padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
	margin-top: 0;
	padding-top: 0;
}
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
	margin-top: 0;
}
li p.first {
	display: inline-block;
}
li {
	margin: 0;
}
ol {
	padding-left: 30px;
    margin: 5px;
    counter-reset: item;
    margin-left: -1px;
    margin-bottom: -1px;
    margin-top: -1px;
}
ol > li {
    counter-increment: item;
    margin-bottom: -1px;
    margin-top: -1px;    
}
ol ol > li {
    display: block;
    margin-bottom: -1px;
    margin-top: -1px;    
}
ol ol > li:before {
    content: counters(item, ".") ". ";
    margin-left: -30px;
    margin-bottom: -1px;
    margin-top: -1px;    
}
ul :first-child, ol :first-child {
	margin-top: 0;
}
ul ul { 
    margin-left: -15px;
}
dl {
	padding: 0;
}
dl dt {
	font-size: 14px;
	font-weight: bold;
	font-style: italic;
	padding: 0;
	margin: 15px 0 5px;
}
dl dt:first-child {
	padding: 0;
}
dl dt> :first-child {
	margin-top: 0;
}
dl dt> :last-child {
	margin-bottom: 0;
}
dl dd {
	margin: 0 0 15px;
	padding: 0 15px;
}
dl dd> :first-child {
	margin-top: 0;
}
dl dd> :last-child {
	margin-bottom: 0;
}
blockquote {
	border-left: 4px solid #dddddd;
	padding: 0 15px;
	color: #777777;
}
blockquote> :first-child {
	margin-top: 0;
}
blockquote> :last-child {
	margin-bottom: 0;
}
table {
	padding: 0;
	border-collapse: collapse;
}
table tr {
	border-top: 1px solid #cccccc;
	background-color: white;
	margin: 0;
	padding: 0;
}
table tr:nth-child(2n) {
	background-color: #f8f8f8;
}
table tr th {
	font-weight: bold;
	border: 1px solid #cccccc;
	margin: 0;
	padding: 6px 13px;
}
table tr td {
	border: 1px solid #cccccc;
	margin: 0;
	padding: 6px 13px;
}
table tr th :first-child, table tr td :first-child {
	margin-top: 0;
}
table tr th :last-child, table tr td :last-child {
	margin-bottom: 0;
}
td {
	vertical-align: top;
}
img {
	max-width: 100%;
}
span.frame {
	display: block;
	overflow: hidden;
}
span.frame>span {
	border: 1px solid #dddddd;
	display: block;
	float: left;
	overflow: hidden;
	margin: 13px 0 0;
	padding: 7px;
	width: auto;
}
span.frame span img {
	display: block;
	float: left;
}
span.frame span span {
	clear: both;
	color: #333333;
	display: block;
	padding: 5px 0 0;
}
span.align-center {
	display: block;
	overflow: hidden;
	clear: both;
}
span.align-center>span {
	display: block;
	overflow: hidden;
	margin: 13px auto 0;
	text-align: center;
}
span.align-center span img {
	margin: 0 auto;
	text-align: center;
}
span.align-right {
	display: block;
	overflow: hidden;
	clear: both;
}
span.align-right>span {
	display: block;
	overflow: hidden;
	margin: 13px 0 0;
	text-align: right;
}
span.align-right span img {
	margin: 0;
	text-align: right;
}
span.float-left {
	display: block;
	margin-right: 13px;
	overflow: hidden;
	float: left;
}
span.float-left span {
	margin: 13px 0 0;
}
span.float-right {
	display: block;
	margin-left: 13px;
	overflow: hidden;
	float: right;
}
span.float-right>span {
	display: block;
	overflow: hidden;
	margin: 13px auto 0;
	text-align: right;
}
code, tt {
	margin: 0 2px;
	padding: 0 5px;
	white-space: nowrap;
	border: 1px solid #eaeaea;
	background-color: #f8f8f8;
	border-radius: 3px;
}
pre code {
	margin: 0;
	padding: 0;
	white-space: pre;
	border: none;
	background: transparent;
}
.highlight pre {
	background-color: #f8f8f8;
	border: 1px solid #cccccc;
	font-size: 13px;
	line-height: 19px;
	overflow: auto;
	padding: 6px 10px;
	border-radius: 3px;
}
pre {
	background-color: #f8f8f8;
	border: 1px solid #cccccc;
	font-size: 13px;
	line-height: 19px;
	overflow: auto;
    overflow-x: hidden;
    overflow-y: hidden;
    padding: 6px 10px;
	border-radius: 3px;
}
pre code, pre tt {
	background-color: transparent;
	border: none;
}
sup {
	font-size: 0.83em;
	vertical-align: super;
	line-height: 0;
}
kbd {
	display: inline-block;
	padding: 3px 5px;
	font-size: 11px;
	line-height: 10px;
	color: #555;
	vertical-align: middle;
	background-color: #fcfcfc;
	border: solid 1px #ccc;
	border-bottom-color: #bbb;
	border-radius: 3px;
	box-shadow: inset 0 -1px 0 #bbb
}
* {
	-webkit-print-color-adjust: exact;
}
ins {
	color: #00A000
}
del {
	color: #A00000
}
a.self-link {
    position: absolute;
    top: 0;
    left: calc(-1 * (3.5rem - 26px));
    width: calc(3.5rem - 26px);
    height: 2em;
    text-align: center;
    border: none;
    transition: opacity .2s;
    opacity: .5;
    font-size: 83%;
}
a.self-link:hover {
    opacity: 1;
}
a.self-link::before {
    content: "§";
}</style>
<style type="text/css">/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+c+cpp&plugins=line-highlight */
/**
 * prism.js default theme for JavaScript, CSS and HTML
 * Based on dabblet (http://dabblet.com)
 * @author Lea Verou
 */

code[class*="language-"],
pre[class*="language-"] {
	color: black;
	background: none;
	text-shadow: 0 1px white;
	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	word-wrap: normal;
	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
	text-shadow: none;
	background: #b3d4fc;
}

pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
	text-shadow: none;
	background: #b3d4fc;
}

@media print {
	code[class*="language-"],
	pre[class*="language-"] {
		text-shadow: none;
	}
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #f8f8f8;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
	white-space: normal;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #999;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
	color: #905;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
	color: #690;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #9a6e3a;
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #07a;
}

.token.function,
.token.class-name {
	color: #DD4A68;
}

.token.regex,
.token.important,
.token.variable {
	color: #e90;
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}

pre[data-line] {
	position: relative;
	padding: 1em 0 1em 3em;
}

.line-highlight {
	position: absolute;
	left: 0;
	right: 0;
	padding: inherit 0;
	margin-top: 1em; /* Same as .prism’s padding-top */

	background: hsla(24, 20%, 50%,.08);
	background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));

	pointer-events: none;

	line-height: inherit;
	white-space: pre;
}

	.line-highlight:before,
	.line-highlight[data-end]:after {
		content: attr(data-start);
		position: absolute;
		top: .4em;
		left: .6em;
		min-width: 1em;
		padding: 0 .5em;
		background-color: hsla(24, 20%, 50%,.4);
		color: hsl(24, 20%, 95%);
		font: bold 65%/1.5 sans-serif;
		text-align: center;
		vertical-align: .3em;
		border-radius: 999px;
		text-shadow: none;
		box-shadow: 0 1px white;
	}

	.line-highlight[data-end]:after {
		content: attr(data-end);
		top: auto;
		bottom: .4em;
	}

.line-numbers .line-highlight:before,
.line-numbers .line-highlight:after {
	content: none;
}

</style>
<script type="text/javascript">/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+c+cpp+nasm+rust&plugins=line-highlight */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof r?new r(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e,t){var r=n.util.type(e);switch(t=t||{},r){case"Object":if(t[n.util.objId(e)])return t[n.util.objId(e)];var a={};t[n.util.objId(e)]=a;for(var l in e)e.hasOwnProperty(l)&&(a[l]=n.util.clone(e[l],t));return a;case"Array":if(t[n.util.objId(e)])return t[n.util.objId(e)];var a=[];return t[n.util.objId(e)]=a,e.forEach(function(e,r){a[r]=n.util.clone(e,t)}),a}return e}},languages:{extend:function(e,t){var r=n.util.clone(n.languages[e]);for(var a in t)r[a]=t[a];return r},insertBefore:function(e,t,r,a){a=a||n.languages;var l=a[e];if(2==arguments.length){r=arguments[1];for(var i in r)r.hasOwnProperty(i)&&(l[i]=r[i]);return l}var o={};for(var s in l)if(l.hasOwnProperty(s)){if(s==t)for(var i in r)r.hasOwnProperty(i)&&(o[i]=r[i]);o[s]=l[s]}var u=a[e];return a[e]=o,n.languages.DFS(n.languages,function(t,n){n===u&&t!=e&&(this[t]=o)}),o},DFS:function(e,t,r,a){a=a||{};for(var l in e)e.hasOwnProperty(l)&&(t.call(e,l,e[l],r||l),"Object"!==n.util.type(e[l])||a[n.util.objId(e[l])]?"Array"!==n.util.type(e[l])||a[n.util.objId(e[l])]||(a[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,l,a)):(a[n.util.objId(e[l])]=!0,n.languages.DFS(e[l],t,null,a)))}},plugins:{},highlightAll:function(e,t){n.highlightAllUnder(document,e,t)},highlightAllUnder:function(e,t,r){var a={callback:r,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};n.hooks.run("before-highlightall",a);for(var l,i=a.elements||e.querySelectorAll(a.selector),o=0;l=i[o++];)n.highlightElement(l,t===!0,a.callback)},highlightElement:function(t,r,a){for(var l,i,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1].toLowerCase(),i=n.languages[l]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,t.parentNode&&(o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l));var s=t.textContent,u={element:t,language:l,grammar:i,code:s};if(n.hooks.run("before-sanity-check",u),!u.code||!u.grammar)return u.code&&(n.hooks.run("before-highlight",u),u.element.textContent=u.code,n.hooks.run("after-highlight",u)),n.hooks.run("complete",u),void 0;if(n.hooks.run("before-highlight",u),r&&_self.Worker){var g=new Worker(n.filename);g.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,a&&a.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},g.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,a&&a.call(t),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,a){var l={code:e,grammar:t,language:a};return n.hooks.run("before-tokenize",l),l.tokens=n.tokenize(l.code,l.grammar),n.hooks.run("after-tokenize",l),r.stringify(n.util.encode(l.tokens),l.language)},matchGrammar:function(e,t,r,a,l,i,o){var s=n.Token;for(var u in r)if(r.hasOwnProperty(u)&&r[u]){if(u==o)return;var g=r[u];g="Array"===n.util.type(g)?g:[g];for(var c=0;c<g.length;++c){var h=g[c],f=h.inside,d=!!h.lookbehind,m=!!h.greedy,p=0,y=h.alias;if(m&&!h.pattern.global){var v=h.pattern.toString().match(/[imuy]*$/)[0];h.pattern=RegExp(h.pattern.source,v+"g")}h=h.pattern||h;for(var b=a,k=l;b<t.length;k+=t[b].length,++b){var w=t[b];if(t.length>e.length)return;if(!(w instanceof s)){if(m&&b!=t.length-1){h.lastIndex=k;var _=h.exec(e);if(!_)break;for(var j=_.index+(d?_[1].length:0),P=_.index+_[0].length,A=b,x=k,O=t.length;O>A&&(P>x||!t[A].type&&!t[A-1].greedy);++A)x+=t[A].length,j>=x&&(++b,k=x);if(t[b]instanceof s)continue;I=A-b,w=e.slice(k,x),_.index-=k}else{h.lastIndex=0;var _=h.exec(w),I=1}if(_){d&&(p=_[1]?_[1].length:0);var j=_.index+p,_=_[0].slice(p),P=j+_.length,N=w.slice(0,j),S=w.slice(P),C=[b,I];N&&(++b,k+=N.length,C.push(N));var E=new s(u,f?n.tokenize(_,f):_,y,_,m);if(C.push(E),S&&C.push(S),Array.prototype.splice.apply(t,C),1!=I&&n.matchGrammar(e,t,r,b,k,!0,u),i)break}else if(i)break}}}}},tokenize:function(e,t){var r=[e],a=t.rest;if(a){for(var l in a)t[l]=a[l];delete t.rest}return n.matchGrammar(e,r,t,0,0,!1),r},hooks:{all:{},add:function(e,t){var r=n.hooks.all;r[e]=r[e]||[],r[e].push(t)},run:function(e,t){var r=n.hooks.all[e];if(r&&r.length)for(var a,l=0;a=r[l++];)a(t)}}},r=n.Token=function(e,t,n,r,a){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length,this.greedy=!!a};if(r.stringify=function(e,t,a){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return r.stringify(n,t,e)}).join("");var l={type:e.type,content:r.stringify(e.content,t,a),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:a};if(e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,"&quot;")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+"</"+l.tag+">"},!_self.document)return _self.addEventListener?(n.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),r=t.language,a=t.code,l=t.immediateClose;_self.postMessage(n.highlight(a,n.languages[r],r)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return a&&(n.filename=a.src,n.manual||a.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:/<!DOCTYPE[\s\S]+?>/i,cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/(^|[^\\])["']/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
Prism.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(?:;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^{}\s][^{};]*?(?=\s*\{)/,string:{pattern:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.languages.css,Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<style[\s\S]*?>)[\s\S]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css",greedy:!0}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(?:true|false)\b/,"function":/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
Prism.languages.c=Prism.languages.extend("clike",{keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*\/%&|^!=<>]=?/,number:/(?:\b0x[\da-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+(?:[^\r\n\\]|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(?:<.+?>|("|')(?:\\?.)+?\2)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(?:define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c["class-name"],delete Prism.languages.c["boolean"];
Prism.languages.cpp=Prism.languages.extend("c",{keyword:/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|concept|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,"boolean":/\b(?:true|false)\b/,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*\/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/}),Prism.languages.insertBefore("cpp","keyword",{"class-name":{pattern:/(class\s+)\w+/i,lookbehind:!0}}),Prism.languages.insertBefore("cpp","string",{"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}});
Prism.languages.nasm={comment:/;.*$/m,string:/(["'`])(?:\\.|(?!\1)[^\\\r\n])*\1/,label:{pattern:/(^\s*)[A-Za-z._?$][\w.?$@~#]*:/m,lookbehind:!0,alias:"function"},keyword:[/\[?BITS (?:16|32|64)\]?/,{pattern:/(^\s*)section\s*[a-zA-Z.]+:?/im,lookbehind:!0},/(?:extern|global)[^;\r\n]*/i,/(?:CPU|FLOAT|DEFAULT).*$/m],register:{pattern:/\b(?:st\d|[xyz]mm\d\d?|[cdt]r\d|r\d\d?[bwd]?|[er]?[abcd]x|[abcd][hl]|[er]?(?:bp|sp|si|di)|[cdefgs]s)\b/i,alias:"variable"},number:/(?:\b|(?=\$))(?:0[hx][\da-f]*\.?[\da-f]+(?:p[+-]?\d+)?|\d[\da-f]+[hx]|\$\d[\da-f]*|0[oq][0-7]+|[0-7]+[oq]|0[by][01]+|[01]+[by]|0[dt]\d+|\d*\.?\d+(?:\.?e[+-]?\d+)?[dt]?)\b/i,operator:/[\[\]*+\-\/%<>=&|$!]/};
Prism.languages.rust={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:[{pattern:/b?r(#*)"(?:\\.|(?!"\1)[^\\\r\n])*"\1/,greedy:!0},{pattern:/b?"(?:\\.|[^\\\r\n"])*"/,greedy:!0}],"char":{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u{(?:[\da-fA-F]_*){1,6}|.)|[^\\\r\n\t'])'/,alias:"string"},"lifetime-annotation":{pattern:/'[^\s>']+/,alias:"symbol"},keyword:/\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,attribute:{pattern:/#!?\[.+?\]/,greedy:!0,alias:"attr-name"},"function":[/\w+(?=\s*\()/,/\w+!(?=\s*\(|\[)/],"macro-rules":{pattern:/\w+!/,alias:"function"},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64)?|f32|f64))?\b/,"closure-params":{pattern:/\|[^|]*\|(?=\s*[{-])/,inside:{punctuation:/[|:,]/,operator:/[&*]/}},punctuation:/[{}[\];(),:]|\.+|->/,operator:/[-+*\/%!^]=?|=[=>]?|@|&[&=]?|\|[|=]?|<<?=?|>>?=?/};
!function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function t(e,t){return t=" "+t+" ",(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)>-1}function n(e,n,i){n="string"==typeof n?n:e.getAttribute("data-line");for(var o,l=n.replace(/\s+/g,"").split(","),a=+e.getAttribute("data-line-offset")||0,s=r()?parseInt:parseFloat,d=s(getComputedStyle(e).lineHeight),u=t(e,"line-numbers"),c=0;o=l[c++];){var p=o.split("-"),m=+p[0],f=+p[1]||m,h=e.querySelector('.line-highlight[data-range="'+o+'"]')||document.createElement("div");if(h.setAttribute("aria-hidden","true"),h.setAttribute("data-range",o),h.className=(i||"")+" line-highlight",u&&Prism.plugins.lineNumbers){var g=Prism.plugins.lineNumbers.getLine(e,m),y=Prism.plugins.lineNumbers.getLine(e,f);g&&(h.style.top=g.offsetTop+"px"),y&&(h.style.height=y.offsetTop-g.offsetTop+y.offsetHeight+"px")}else h.setAttribute("data-start",m),f>m&&h.setAttribute("data-end",f),h.style.top=(m-a-1)*d+"px",h.textContent=new Array(f-m+2).join(" \n");u?e.appendChild(h):(e.querySelector("code")||e).appendChild(h)}}function i(){var t=location.hash.slice(1);e(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var i=(t.match(/\.([\d,-]+)$/)||[,""])[1];if(i&&!document.getElementById(t)){var r=t.slice(0,t.lastIndexOf(".")),o=document.getElementById(r);o&&(o.hasAttribute("data-line")||o.setAttribute("data-line",""),n(o,i,"temporary "),document.querySelector(".temporary.line-highlight").scrollIntoView())}}if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var r=function(){var e;return function(){if("undefined"==typeof e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding=0,t.style.border=0,t.innerHTML="&nbsp;<br />&nbsp;",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}}(),o=0;Prism.hooks.add("before-sanity-check",function(t){var n=t.element.parentNode,i=n&&n.getAttribute("data-line");if(n&&i&&/pre/i.test(n.nodeName)){var r=0;e(".line-highlight",n).forEach(function(e){r+=e.textContent.length,e.parentNode.removeChild(e)}),r&&/^( \n)+$/.test(t.code.slice(-r))&&(t.code=t.code.slice(0,-r))}}),Prism.hooks.add("complete",function l(e){var r=e.element.parentNode,a=r&&r.getAttribute("data-line");if(r&&a&&/pre/i.test(r.nodeName)){clearTimeout(o);var s=Prism.plugins.lineNumbers,d=e.plugins&&e.plugins.lineNumbers;t(r,"line-numbers")&&s&&!d?Prism.hooks.add("line-numbers",l):(n(r,a),o=setTimeout(i,1))}}),window.addEventListener("hashchange",i),window.addEventListener("resize",function(){var e=document.querySelectorAll("pre[data-line]");Array.prototype.forEach.call(e,function(e){n(e)})})}}();
</script>

</head>
<body>
<address align=right>
Document Number: P1185R1 <br />
Date: 2019-01-22 <br />
Audience: CWG, EWG <br />
Reply-To: Barry Revzin, barry dot revzin at gmail dot com <br />
</address>
<hr /><h1 align=center><p><code class="language-cpp">&lt;=&gt; != ==</code></p></h1>
<h2>Contents</h2>
<div class="toc">
<ol>
<li><a href="#revision-history">Revision History</a></li>
<li><a href="#motivation">Motivation</a><ol>
<li><a href="#why-this-is-really-bad">Why this is really bad</a></li>
<li><a href="#other-languages">Other Languages</a><ol>
<li><a href="#rust">Rust</a></li>
<li><a href="#other-languages_1">Other Languages</a></li>
</ol>
</li>
</ol>
</li>
<li><a href="#proposal">Proposal</a><ol>
<li><a href="#change-the-candidate-set-for-operator-lookup">Change the candidate set for operator lookup</a></li>
<li><a href="#change-the-meaning-of-defaulted-equality-operators">Change the meaning of defaulted equality operators</a></li>
<li><a href="#change-how-we-define-strong-structural-equality">Change how we define strong structural equality</a></li>
<li><a href="#change-defaulted-to-also-generate-a-defaulted">Change defaulted &lt;=&gt; to also generate a defaulted ==</a></li>
</ol>
</li>
<li><a href="#important-implications">Important implications</a><ol>
<li><a href="#implications-for-types-that-have-special-but-not-different-comparisons">Implications for types that have special, but not different, comparisons</a></li>
<li><a href="#implications-for-comparison-categories">Implications for comparison categories</a></li>
</ol>
</li>
<li><a href="#core-design-questions">Core Design Questions</a></li>
<li><a href="#wording">Wording</a></li>
<li><a href="#acknowledgements">Acknowledgements</a></li>
<li><a href="#references">References</a></li>
</ol>
</div>

<h2 id="revision-history">1. Revision History<a class="self-link" href="#revision-history"></a></h2>
<p>R0 of this paper was approved in its entirety by Evolution in San Diego. This new revision contains brand new wording after core review. There were two <a href="#core-design-questions">design questions</a> brought up by Core during this review, both based on the meaning of implicitly generated <code class="language-cpp">==</code>, which are discussed in this revision.</p>
<h2 id="motivation">2. Motivation<a class="self-link" href="#motivation"></a></h2>
<p><a href="https://wg21.link/p0515r3" title="Consistent comparison">P0515</a> introduced <code class="language-cpp">operator&lt;=&gt;</code> as a way of generating all six comparison operators from a single function, as well as the ability to default this so as to avoid writing any code at all. See David Stone's <a href="https://wg21.link/p1190r0" title="I did not order this! Why is it on my bill?">I did not order this!</a> for a very clear, very thorough description of the problem: it does not seem to be possible to implement <code class="language-cpp">&lt;=&gt;</code> optimally for "wrapper" types. What follows is a super brief run-down.</p>
<p>Consider a type like:</p>
<pre class="codehilite"><code class="language-cpp">struct S {
    vector&lt;string&gt; names;
    auto operator&lt;=&gt;(S const&amp;) const = default;
};</code></pre>


<p>Today, this is ill-formed, because <code class="language-cpp">vector</code> does not implement <code class="language-cpp">&lt;=&gt;</code>. In order to make this work, we need to add that implementation. It is <em>not</em> recommended that <code class="language-cpp">vector</code> only provide <code class="language-cpp">&lt;=&gt;</code>, but we will start there and it will become clear why that is the recommendation.</p>
<p>The most straightforward implementation of <code class="language-cpp">&lt;=&gt;</code> for <code class="language-cpp">vector</code> is (let's just assume <code class="language-cpp">strong_ordering</code> and note that I'm deliberately not using <code class="language-cpp">std::lexicographical_compare_3way()</code> for clarity):</p>
<pre class="codehilite"><code class="language-cpp">template&lt;typename T&gt;
strong_ordering operator&lt;=&gt;(vector&lt;T&gt; const&amp; lhs, vector&lt;T&gt; const&amp; rhs) {
    size_t min_size = min(lhs.size(), rhs.size());
    for (size_t i = 0; i != min_size; ++i) {
        if (auto const cmp = compare_3way(lhs[i], rhs[i]); cmp != 0) {
            return cmp;
        }
    }
    return lhs.size() &lt;=&gt; rhs.size();
}</code></pre>


<p>On the one hand, this is great. We wrote one function instead of six, and this function is really easy to understand too. On top of that, this is a really good implementation for <code class="language-cpp">&lt;</code>!  As good as you can get. And our code for <code class="language-cpp">S</code> works (assuming we do something similar for <code class="language-cpp">string</code>).</p>
<p>On the other hand, as David goes through in a lot of detail (seriously, read it) this is quite bad for <code class="language-cpp">==</code>. We're failing to short-circuit early on size differences! If two containers have a large common prefix, despite being different sizes, that's an enormous amount of extra work!</p>
<p>In order to do <code class="language-cpp">==</code> efficiently, we have to short-circuit and do <code class="language-cpp">==</code> all the way down. That is:</p>
<pre class="codehilite"><code class="language-cpp">template&lt;typename T&gt;
bool operator==(vector&lt;T&gt; const&amp; lhs, vector&lt;T&gt; const&amp; rhs)
{
    // short-circuit on size early
    const size_t size = lhs.size();
    if (size != rhs.size()) {
        return false;
    }

    for (size_t i = 0; i != size; ++i) {
        // use ==, not &lt;=&gt;, in all nested comparisons
        if (lhs[i] != rhs[i]) {
            return false;
        }
    }

    return true;
}</code></pre>


<h3 id="why-this-is-really-bad">2.1. Why this is really bad<a class="self-link" href="#why-this-is-really-bad"></a></h3>
<p>This is really bad on several levels, significant levels.</p>
<p>First, since <code class="language-cpp">==</code> falls back on <code class="language-cpp">&lt;=&gt;</code>, it's easy to fall into the trap that once <code class="language-cpp">v1 == v2</code> compiles and gives the correct answer, we're done. If we didn't implement the efficient <code class="language-cpp">==</code>, outside of very studious code review, we'd have no way of finding out. The problem is that <code class="language-cpp">v1 &lt;=&gt; v2 == 0</code> would always give the <em>correct</em> answer (assuming we correctly implemented <code class="language-cpp">&lt;=&gt;</code>). How do you write a test to ensure that we did the short circuiting? The only way you could do it is to time some pathological case - comparing a vector containing a million entries against a vector containing those same million entries plus <code class="language-cpp">1</code> - and checking if it was fast?</p>
<p>Second, the above isn't even complete yet. Because even if we were careful enough to write <code class="language-cpp">==</code>, we'd get an efficient <code class="language-cpp">v1 == v2</code>... but still an inefficient <code class="language-cpp">v1 != v2</code>, because that one would call <code class="language-cpp">&lt;=&gt;</code>. We would have to also write this manually:</p>
<pre class="codehilite"><code class="language-cpp">template&lt;typename T&gt;
bool operator!=(vector&lt;T&gt; const&amp; lhs, vector&lt;T&gt; const&amp; rhs)
{
    return !(lhs == rhs);
}</code></pre>


<p>Third, this compounds <em>further</em> for any types that have something like this as a member. Getting back to our <code class="language-cpp">S</code> above:</p>
<pre class="codehilite"><code class="language-cpp">struct S {
    vector&lt;string&gt; names;
    auto operator&lt;=&gt;(S const&amp;) const = default;
};</code></pre>


<p>Even if we correctly implemented <code class="language-cpp">==</code>, <code class="language-cpp">!=</code>, and <code class="language-cpp">&lt;=&gt;</code> for <code class="language-cpp">vector</code> and <code class="language-cpp">string</code>, comparing two <code class="language-cpp">S</code>s for equality <em>still</em> calls <code class="language-cpp">&lt;=&gt;</code> and is <em>still</em> a completely silent pessimization. Which <em>again</em> we cannot test functionally, only with a timer.</p>
<p>And then, it somehow gets even worse, because it's be easy to fall into yet another trap: you somehow have the diligence to remember that you need to explicitly define <code class="language-cpp">==</code> for this type and you do it this way:</p>
<pre class="codehilite"><code class="language-cpp">struct S {
    vector&lt;string&gt; names;
    auto operator&lt;=&gt;(S const&amp;) const = default;
    bool operator==(S const&amp;) const = default; // problem solved, right?
};</code></pre>


<p>But what does defaulting <code class="language-cpp">operator==</code> actually do? It <a href="http://eel.is/c++draft/class.rel.eq" title="[class.rel.eq]">invokes <code class="language-cpp">&lt;=&gt;</code></a>. So here's explicit code that seems sensible to add to attempt to address this problem, that does absolutely nothing to address this problem. </p>
<p>The only way to get efficiency is to have every type, even <code class="language-cpp">S</code> above, implement both not just <code class="language-cpp">&lt;=&gt;</code> but also <code class="language-cpp">==</code> and <code class="language-cpp">!=</code>. By hand. </p>
<pre class="codehilite"><code class="language-cpp">struct S {
    vector&lt;string&gt; names;
    auto operator&lt;=&gt;(S const&amp;) const = default;
    bool operator==(S const&amp; rhs) const { return names == rhs.names; }
    bool operator!=(S const&amp; rhs) const { return names != rhs.names; }
};</code></pre>


<p>That is the status quo today and the problem that needs to be solved.</p>
<h3 id="other-languages">2.2. Other Languages<a class="self-link" href="#other-languages"></a></h3>
<p>In order how to best figure out how to solve this problem for C++, it is helpful to look at how other languages have already addressed this issue. While P0515 listed many languages which have a three-way comparison returning a signed integer, there is another set of otherwise mostly-unrelated languages that take a different approach. </p>
<h4 id="rust">2.2.1. Rust<a class="self-link" href="#rust"></a></h4>
<p>Rust, Kotlin, Swift, Haskell, and Scala are rather different languages in many respects. But they all solve this particular problem in basically the same way: they treat <em>equality</em> and <em>comparison</em> as separate operations. I want to focus specifically on Rust here as it's arguably the closest language to C++ of the group, but the other three are largely equivalent for the purposes of this specific discussion.</p>
<p>Rust deals in Traits (which are roughly analogous to C++0x concepts and Swift protocols) and it has four relevant Traits that have to do with comparisons:</p>
<ul>
<li><code class="language-cpp">PartialEq</code> (which is a partial equivalence relation spelled which only requires symmetry and transitivity)</li>
<li><code class="language-cpp">Eq</code> (which extends <code class="language-cpp">PartialEq</code>, adding reflexivity)</li>
<li><code class="language-cpp">PartialOrd</code> (which allows for incomparability by returning <code class="language-cpp">Option&lt;Ordering&gt;</code>, where <code class="language-cpp">Ordering</code> is an enum)</li>
<li><code class="language-cpp">Ord</code> (a total order, which extends <code class="language-cpp">Eq</code> and <code class="language-cpp">PartialOrd</code>)</li>
</ul>
<p>The actual operators are <a href="https://doc.rust-lang.org/reference/expressions/operator-expr.html#comparison-operators" title="Comparison Operators - The Rust Reference">implicitly generated</a> from these traits, but not all from the same one. Importantly, <code class="language-cpp">x == y</code> is translated as <code class="language-cpp">PartialEq::eq(x, y)</code> whereas <code class="language-cpp">x &lt; y</code> is translated as <code class="language-cpp">PartialOrd::lt(x, y)</code> (which is effectively checking that <code class="language-cpp">PartialOrd::partial_cmp(x, y)</code> is <code class="language-cpp">Less</code>).</p>
<p>That is, you don't get <em>six</em> functions for the price of one. You need to write <em>two functions</em>. </p>
<p>Even if you don't know Rust (and I really don't know Rust), I think it would be instructive here would be to look at how the equivalent comparisons are implemented for Rust's <code class="language-cpp">vector</code> type. The important parts look like this:</p>
<table style="width:100%">
<tr>
<th style="width:50%">
<p><a href="https://doc.rust-lang.org/src/core/slice/mod.rs.html?search=#4037-4053" title="Implementation of Eq for Slice"><code class="language-cpp">Eq</code></a></p>
</th>
<th>
<p><a href="https://doc.rust-lang.org/src/core/slice/mod.rs.html#4116-4136" title="Implementation of Ord for Slice"><code class="language-cpp">Ord</code></a></p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px" data-line="5,6,7,10"><code class="language-rust">impl&lt;A, B&gt; SlicePartialEq&lt;B&gt; for [A]
    where A: PartialEq&lt;B&gt;
{
    default fn eq(&amp;self, other: &amp;[B]) -&gt; bool {
        if self.len() != other.len() {
            return false;
        }

        for i in 0..self.len() {
            if !self[i].eq(&amp;other[i]) {
                return false;
            }
        }

        true
    }
}</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px" data-line="11"><code class="language-rust">impl&lt;A&gt; SliceOrd&lt;A&gt; for [A]
    where A: Ord
{
    default fn cmp(&amp;self, other: &amp;[A]) -&gt; Ordering {
        let l = cmp::min(self.len(), other.len());

        let lhs = &amp;self[..l];
        let rhs = &amp;other[..l];

        for i in 0..l {
            match lhs[i].cmp(&amp;rhs[i]) {
                Ordering::Equal =&gt; (),
                non_eq =&gt; return non_eq,
            }
        }

        self.len().cmp(&amp;other.len())
    }
}</code></pre>
</td>
</tr>
</table>

<p>In other words, <code class="language-cpp">eq</code> calls <code class="language-cpp">eq</code> all the way down while doing short-circuiting whereas <code class="language-cpp">cmp</code> calls <code class="language-cpp">cmp</code> all the way down, and these are two separate functions. Both algorithms exactly match our implementation of <code class="language-cpp">==</code> and <code class="language-cpp">&lt;=&gt;</code> for <code class="language-cpp">vector</code> above. Even though <code class="language-cpp">cmp</code> performs a 3-way ordering, and you can use the result of <code class="language-cpp">a.cmp(b)</code> to determine that <code class="language-cpp">a == b</code>, it is <em>not</em> the way that Rust (or other languages in this realm like Swift and Kotlin and Haskell) determine equality. </p>
<h4 id="other-languages_1">2.2.2. Other Languages<a class="self-link" href="#other-languages_1"></a></h4>
<p>Swift has <a href="https://developer.apple.com/documentation/swift/equatable" title="Equatable - Swift Standard Library"><code class="language-cpp">Equatable</code></a> and <a href="https://developer.apple.com/documentation/swift/comparable" title="Comparable - Swift Standard Library"><code class="language-cpp">Comparable</code></a> protocols. For types that conform to <code class="language-cpp">Equatable</code>, <code class="language-cpp">!=</code> is implicitly generated from <code class="language-cpp">==</code>. For types that conform to <code class="language-cpp">Comparable</code>, <code class="language-cpp">&gt;</code>, <code class="language-cpp">&gt;=</code>, and <code class="language-cpp">&lt;=</code> are implicitly generated from <code class="language-cpp">&lt;</code>. Swift does not have a 3-way comparison function.</p>
<p>There are other languages that make roughly the same decision in this regard that Rust does: <code class="language-cpp">==</code> and <code class="language-cpp">!=</code> are generated from a function that does equality whereas the four relational operators are generated from a three-way comparison. Even though the three-way comparison <em>could</em> be used to determine equality, it is not:</p>
<ul>
<li>Kotlin, like Java, has a <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-comparable/index.html" title="Comparable - Kotlin Programming Language"><code class="language-cpp">Comparable</code></a> interface and a separate <code class="language-cpp">equals</code> method inherited from <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html" title="Any - Kotlin Programming Language"><code class="language-cpp">Any</code></a>. Unlike Java, it has <a href="https://kotlinlang.org/docs/reference/operator-overloading.html#equals" title="Operator overloading - Kotlin Programming Language">operator overloading</a>: <code class="language-cpp">a == b</code> means <code class="language-cpp">a?.equals(b) ?: (b === null)</code> and <code class="language-cpp">a &lt; b</code> means <code class="language-cpp">a.compareTo(b) &lt; 0</code>.</li>
<li>Haskell has the <a href="http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Eq.html" title="Data.Eq - Haskell documentation"><code class="language-cpp">Data.Eq</code></a> and <a href="http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Ord.html" title="Data.Ord - Haskell documentation"><code class="language-cpp">Data.Ord</code></a> type classes. <code class="language-cpp">!=</code> is generated from <code class="language-cpp">==</code> (or vice versa, depending on which definition is provided for <code class="language-cpp">Eq</code>). If a <code class="language-cpp">compare</code> method is provided to conform to <code class="language-cpp">Ord</code>, <code class="language-cpp">a &lt; b</code> means <code class="language-cpp">(compare a b) &lt; 0</code>.</li>
<li>Scala's equality operators come from the root <a href="https://www.scala-lang.org/api/current/scala/Any.html#==(x$1:Any):Boolean" title="Scala Standard Library - Any"><code class="language-cpp">Any</code></a> interface, <code class="language-cpp">a == b</code> means <code class="language-cpp">if (a eq null) b eq null else a.equals(b)</code>. Its relational operators come from the <a href="https://www.scala-lang.org/api/current/scala/math/Ordered.html" title="Scala Standard Library - Ordered"><code class="language-cpp">Ordered</code></a> trait, where <code class="language-cpp">a &lt; b</code> means <code class="language-cpp">(a compare b) &lt; 0</code>.</li>
</ul>
<h2 id="proposal">3. Proposal<a class="self-link" href="#proposal"></a></h2>
<p>Fundamentally, we have two sets of operations: equality and comparison. In order to be efficient and not throw away performance, we need to implement them separately. <code class="language-cpp">operator&lt;=&gt;()</code> as specified in the working draft today generating all six functions just doesn't seem to be a good solution.</p>
<p>This paper proposes to do something similar to the Rust model above and first described in <a href="https://github.com/davidstone/isocpp/blob/master/operator-spaceship/I-did-not-order-this.md#make-operator-create-only-operator-operator-operator-and-operator">this section</a> of the previously linked paper: require two separate functions to implement all the functionality. </p>
<p>The proposal has two core components:</p>
<ul>
<li>change the candidate set for operator lookup</li>
<li>change the meaning of defaulted equality operators</li>
</ul>
<p>And two optional components:</p>
<ul>
<li>change how we define <a href="http://eel.is/c++draft/class.compare#def:equality,strong_structural" title="[class.compare]"><em>strong structural equality</em></a>, which is important for <a href="https://wg21.link/p0732r2" title="Class Types in Non-Type Template Parameters">P0732R2</a></li>
<li>change defaulted <code class="language-cpp">&lt;=&gt;</code> to also generate a defaulted <code class="language-cpp">==</code></li>
</ul>
<h3 id="change-the-candidate-set-for-operator-lookup">3.1. Change the candidate set for operator lookup<a class="self-link" href="#change-the-candidate-set-for-operator-lookup"></a></h3>
<p>Today, lookup for any of the relational and equality operators will also consider <a href="http://eel.is/c++draft/over.match.oper#3.4"><code class="language-cpp">operator&lt;=&gt;</code></a>, but preferring <a href="http://eel.is/c++draft/over.match.best#1.10">the actual used operator</a>. </p>
<p>The proposed change is for the equality operators to <em>not</em> consider <code class="language-cpp">&lt;=&gt;</code> candidates. Instead, inequality will consider equality as a candidate. In other words, here is the proposed set of candidates. There are no changes proposed for the relational operators, only for the equality ones:</p>
<table>
<th>
<p>Source <br />
<code class="language-cpp">a @ b</code></p>
</th>
<th>
<p>Today (P0515/C++2a)</p>
</th>
<th>
<p>Proposed</p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a == b</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a == b
(a &lt;=&gt; b) == 0
0 == (b &lt;=&gt; a)</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a == b
b == a</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a != b</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a != b
(a &lt;=&gt; b) != 0
0 != (a &lt;=&gt; b)</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a != b
!(a == b)
!(b == a)</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a &lt; b</code></pre>
</td>
<td colspan="2">
<pre style="background:transparent;border:0px"><code class="language-cpp">a &lt; b
(a &lt;=&gt; b) &lt; 0
0 &lt; (b &lt;=&gt; a)</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a &lt;= b</code></pre>
</td>
<td colspan="2">
<pre style="background:transparent;border:0px"><code class="language-cpp">a &lt;= b
(a &lt;=&gt; b) &lt;= 0
0 &lt;= (b &lt;=&gt; a)</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a &gt; b</code></pre>
</td>
<td colspan="2">
<pre style="background:transparent;border:0px"><code class="language-cpp">a &gt; b
(a &lt;=&gt; b) &gt; 0
0 &gt; (b &lt;=&gt; a)</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">a &gt;= b</code></pre>
</td>
<td colspan="2">
<pre style="background:transparent;border:0px"><code class="language-cpp">a &gt;= b
(a &lt;=&gt; b) &gt;= 0
0 &gt;= (b &lt;=&gt; a)</code></pre>
</td>
</tr>
</table>

<p>In short, <code class="language-cpp">==</code> and <code class="language-cpp">!=</code> never invoke <code class="language-cpp">&lt;=&gt;</code> implicitly. </p>
<h3 id="change-the-meaning-of-defaulted-equality-operators">3.2. Change the meaning of defaulted equality operators<a class="self-link" href="#change-the-meaning-of-defaulted-equality-operators"></a></h3>
<p>As mentioned earlier, in the current working draft, defaulting <code class="language-cpp">==</code> or <code class="language-cpp">!=</code> generates a function that invokes <code class="language-cpp">&lt;=&gt;</code>. This paper proposes that defaulting <code class="language-cpp">==</code> generates a member-wise equality comparison and that defaulting <code class="language-cpp">!=</code> generate a call to negated <code class="language-cpp">==</code>.</p>
<p>That is:</p>
<table style="width:100%">
<tr>
<th style="width:33%">
<p>Sample Code</p>
</th>
<th style="width:33%">
<p>Meaning Today (P0515/C++2a)</p>
</th>
<th>
<p>Proposed Meaning</p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct X {
  A a;
  B b;
  C c;

  auto operator&lt;=&gt;(X const&amp;) const = default;
  bool operator==(X const&amp;) const = default;
  bool operator!=(X const&amp;) const = default;
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct X {
  A a;
  B b;
  C c;

  ??? operator&lt;=&gt;(X const&amp; rhs) const {
    if (auto cmp = a &lt;=&gt; rhs.a; cmp != 0)
      return cmp;
    if (auto cmp = b &lt;=&gt; rhs.b; cmp != 0)
      return cmp;
    return c &lt;=&gt; rhs.c;
  }

  bool operator==(X const&amp; rhs) const {
    return (*this &lt;=&gt; rhs) == 0;
  }

  bool operator!=(X const&amp; rhs) const {
    return (*this &lt;=&gt; rhs) != 0;
  }
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct X {
  A a;
  B b;
  C c;

  ??? operator&lt;=&gt;(X const&amp; rhs) const {
    if (auto cmp = a &lt;=&gt; rhs.a; cmp != 0)
      return cmp;
    if (auto cmp = b &lt;=&gt; rhs.b; cmp != 0)
      return cmp;
    return c &lt;=&gt; rhs.c;
  }

  bool operator==(X const&amp; rhs) const {
    return a == rhs.a &amp;&amp;
      b == rhs.b &amp;&amp;
      c == rhs.c;
  }

  bool operator!=(X const&amp; rhs) const {
    return !(*this == rhs);
  }
};</code></pre>
</td>
</tr>
</table>

<p>These two changes ensure that the equality operators and the relational operators remain segregated. </p>
<h3 id="change-how-we-define-strong-structural-equality">3.3. Change how we define strong structural equality<a class="self-link" href="#change-how-we-define-strong-structural-equality"></a></h3>
<p><a href="https://wg21.link/p0732r2" title="Class Types in Non-Type Template Parameters">P0732R2</a> relies on <em>strong structural equality</em> as the criteria to allow a class to be used as a non-type template parameter - which is based on having a defaulted <code class="language-cpp">&lt;=&gt;</code> that itself only calls defaulted <code class="language-cpp">&lt;=&gt;</code> recursively all the way down and has type either <code class="language-cpp">strong_ordering</code> or <code class="language-cpp">strong_equality</code>.</p>
<p>This criteria clashes somewhat with this proposal, which is fundamentally about not making <code class="language-cpp">&lt;=&gt;</code> be about equality. So it would remain odd if, for instance, we rely on a defaulted <code class="language-cpp">&lt;=&gt;</code> whose return type is <code class="language-cpp">strong_equality</code> (which itself can never be used to determine actual equality).</p>
<p>We have two options here:</p>
<ol>
<li>
<p>Do nothing. Do not change the rules here at all, still require defaulted <code class="language-cpp">&lt;=&gt;</code> for use as a non-type template parameter. This means that there may be types which don't have a natural ordering for which we would have to both default <code class="language-cpp">==</code> and default <code class="language-cpp">&lt;=&gt;</code> (with <code class="language-cpp">strong_equality</code>), the latter being a function that <em>only</em> exists to opt-in to this behavior. </p>
</li>
<li>
<p>Change the definition of strong structural equality to use <code class="language-cpp">==</code> instead. The wording here would have to be slightly more complex: define a type <code class="language-cpp">T</code> as having strong structural equality if each subobject recursively has defaulted <code class="language-cpp">==</code> and none of the subobjects are floating point types. </p>
</li>
</ol>
<p>The impact of this change revolves around the code necessary to write a type that is intended to only be equality-comparable (not ordered) but also usable as a non-type template parameter: only <code class="language-cpp">operator==</code> would be necessary.</p>
<table style="width:100%">
<tr>
<th style="width:50%">
<p>Do nothing</p>
</th>
<th style="width:50%">
<p>Change definition</p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct C {
    int i;
    bool operator==(C const&amp;) const = default;
    strong_equality operator&lt;=&gt;(C const&amp;) const = default;
};

template &lt;C x&gt;
struct Z { };</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct C {
    int i;
    bool operator==(C const&amp;) const = default;
};

template &lt;C x&gt;
struct Z { };</code></pre>
</td>
</tr>
</table>

<h3 id="change-defaulted-to-also-generate-a-defaulted">3.4. Change defaulted <code class="language-cpp">&lt;=&gt;</code> to also generate a defaulted <code class="language-cpp">==</code><a class="self-link" href="#change-defaulted-to-also-generate-a-defaulted"></a></h3>
<p>One of the important consequences of this proposal is that if you simply want lexicographic, member-wise, ordering for your type - you need to default <em>two</em> functions (<code class="language-cpp">==</code> and <code class="language-cpp">&lt;=&gt;</code>) instead of just one (<code class="language-cpp">&lt;=&gt;</code>):</p>
<table style="width:100%">
<tr>
<th style="width:50%">
<p>P0515/C++2a</p>
</th>
<th style="width:50%">
<p>Proposed</p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">// all six
struct A {
    auto operator&lt;=&gt;(A const&amp;) const = default;
};

// just equality, no relational
struct B {
    strong_equality operator&lt;=&gt;(B const&amp;) const = default;
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">// all six
struct A {
    bool operator==(A const&amp;) const = default;
    auto operator&lt;=&gt;(A const&amp;) const = default;
};

// just equality, no relational
struct B {
    bool operator==(B const&amp;) const = default;
};</code></pre>
</td>
</tr>
</table>

<p>Arguably, <code class="language-cpp">A</code> isn't terrible here and <code class="language-cpp">B</code> is somewhat simpler. But it makes this proposal seem like it's fighting against the promise of P0515 of making a trivial opt-in to ordering. </p>
<p>As an optional extension, this paper proposes that a defaulted <code class="language-cpp">&lt;=&gt;</code> operator also generate a defaulted <code class="language-cpp">==</code>. We can do this regardless of whether the return type of the defaulted <code class="language-cpp">&lt;=&gt;</code> is provided or not, since even <code class="language-cpp">weak_equality</code> implies <code class="language-cpp">==</code>.</p>
<p>This change, combined with the core proposal, means that one single defaulted operator is sufficient for full comparison. The difference is that, with this proposal, we still get optimal equality.</p>
<p>This change may also obviate the need for the previous optional extension of changing the definition of strong structural extension. But even still, the changes are worth considering separately. </p>
<h2 id="important-implications">4. Important implications<a class="self-link" href="#important-implications"></a></h2>
<p>This proposal means that for complex types (like containers), we have to write two functions instead of just <code class="language-cpp">&lt;=&gt;</code>. But we really have to do that anyway if we want performance. Even though the two <code class="language-cpp">vector</code> functions are very similar, and for <code class="language-cpp">optional</code> they are even more similar (see below), this seems like a very necessary change.</p>
<p>For compound types (like aggregates), depending on the preference of the previous choices, we either have to default to functions instead or still just default <code class="language-cpp">&lt;=&gt;</code>... but we get optimal performance. </p>
<p>Getting back to our initial example, we would write:</p>
<pre class="codehilite"><code class="language-cpp">struct S {
    vector&lt;string&gt; names;
    bool operator==(S const&amp;) const = default; // (*) if 2.4 not adopted
    auto operator&lt;=&gt;(S const&amp;) const = default;
};</code></pre>


<p>Even if we choose to require defaulting <code class="language-cpp">operator==</code> in this example, the fact that <code class="language-cpp">&lt;=&gt;</code> is no longer considered as a candidate for equality means that the worst case of forgetting this function is that equality <em>does not compile</em>. That is a substantial improvement over the alternative where equality compiles and has subtly worse performance that will be very difficult to catch.</p>
<h3 id="implications-for-types-that-have-special-but-not-different-comparisons">4.1. Implications for types that have special, but not different, comparisons<a class="self-link" href="#implications-for-types-that-have-special-but-not-different-comparisons"></a></h3>
<p>There are many kinds of types for which the defaulted comparison semantics are incorrect, but nevertheless don't have to do anything different between equality and ordering. One such example is <code class="language-cpp">optional&lt;T&gt;</code>. Having to write two functions here is extremely duplicative:</p>
<table style="width:100%">
<tr>
<th style="width:50%">
<p><a href="https://medium.com/@barryrevzin/implementing-the-spaceship-operator-for-optional-4de89fc6d5ec" title="Implementing the spaceship operator for optional">P0515/C++2a</a></p>
</th>
<th style="width:50%">
<p>Proposed</p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">template &lt;typename T, typename U&gt;
constexpr auto operator&lt;=&gt;(optional&lt;T&gt; const&amp; lhs,
        optional&lt;U&gt; const&amp; rhs) const
    -&gt; decltype(compare_3way(*lhs, *rhs))
{
    if (lhs.has_value() &amp;&amp; rhs.has_value()) {
        return compare_3way(*lhs, *rhs);
    } else {
        return lhs.has_value() &lt;=&gt; rhs.has_value();
    }
}</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px" data-line="4,7,9,16,19,21"><code class="language-cpp">template &lt;typename T, typename U&gt;
constexpr auto operator&lt;=&gt;(optional&lt;T&gt; const&amp; lhs,
        optional&lt;U&gt; const&amp; rhs) const
    -&gt; decltype(compare_3way(*lhs, *rhs))
{
    if (lhs.has_value() &amp;&amp; rhs.has_value()) {
        return compare_3way(*lhs, *rhs);
    } else {
        return lhs.has_value() &lt;=&gt; rhs.has_value();
    }
}

template &lt;typename T, typename U&gt;
constexpr auto operator==(optional&lt;T&gt; const&amp; lhs,
        optional&lt;U&gt; const&amp; rhs) const
    -&gt; decltype(*lhs == *rhs)
{
    if (lhs.has_value() &amp;&amp; rhs.has_value()) {
        return *lhs == *rhs;
    } else {
        return lhs.has_value() == rhs.has_value();
    }
}</code></pre>
</td>
</tr>
</table>

<p>As is probably obvious, the implementations of <code class="language-cpp">==</code> and <code class="language-cpp">&lt;=&gt;</code> are basically identical: the only difference is that <code class="language-cpp">==</code> calls <code class="language-cpp">==</code> and <code class="language-cpp">&lt;=&gt;</code> calls <code class="language-cpp">&lt;=&gt;</code> (or really <code class="language-cpp">compare_3way</code>). It may be very tempting to implement <code class="language-cpp">==</code> to just call <code class="language-cpp">&lt;=&gt;</code>, but that would be wrong! It's critical that <code class="language-cpp">==</code> call <code class="language-cpp">==</code> all the way down.</p>
<p>It's important to keep in mind three things.</p>
<ol>
<li>In C++17 we'd have to write six functions, so writing two is a large improvement. </li>
<li>These two functions may be duplicated, but they give us optimal performance - writing the one <code class="language-cpp">&lt;=&gt;</code> to generate all six comparison functions does not. </li>
<li>The amount of special types of this kind - types that have non-default comparison behavior but perform the same algorithm for both <code class="language-cpp">==</code> and <code class="language-cpp">&lt;=&gt;</code> - is fairly small. Most container types would have separate algorithms. Typical types default both, or just default <code class="language-cpp">==</code>. The canonical examples that would need special behavior are <code class="language-cpp">std::array</code> and <code class="language-cpp">std::forward_list</code> (which either have fixed or unknown size and thus cannot short-circuit) and <code class="language-cpp">std::optional</code> and <code class="language-cpp">std::variant</code> (which can't do default comparison). So this particular duplication is a fairly limited problem.</li>
</ol>
<h3 id="implications-for-comparison-categories">4.2. Implications for comparison categories<a class="self-link" href="#implications-for-comparison-categories"></a></h3>
<p>One of the features of P0515 is that you could default <code class="language-cpp">&lt;=&gt;</code> to, instead of returning an order, simply return some kind of equality:</p>
<pre class="codehilite"><code class="language-cpp">struct X {
    std::strong_equality operator&lt;=&gt;(X const&amp;) const = default;
};</code></pre>


<p>In a world where neither <code class="language-cpp">==</code> nor <code class="language-cpp">!=</code> would be generated from <code class="language-cpp">&lt;=&gt;</code>, this no longer makes much sense. We could have to require that the return type of <code class="language-cpp">&lt;=&gt;</code> be some kind of ordering - that is, at least <code class="language-cpp">std::partial_ordering</code>. Allowing the declaration of <code class="language-cpp">X</code> above would be misleading, at best. </p>
<p>This means there may not be a way to differentiate between <code class="language-cpp">std::strong_equality</code> and <code class="language-cpp">std::weak_equality</code>. The only other place to do this kind of differentiation would be if we somehow allowed it in the return of <code class="language-cpp">operator==</code>:</p>
<pre class="codehilite"><code class="language-cpp">struct X {
    std::strong_equality operator==(X const&amp;) const = default;
};</code></pre>


<p>And I'm not sure this makes any sense. </p>
<h2 id="core-design-questions">5. Core Design Questions<a class="self-link" href="#core-design-questions"></a></h2>
<p>The rule that this paper proposes, that EWG approved, was that if a class has an explicitly defaulted <code class="language-cpp">&lt;=&gt;</code> operator function then that class will also get an implicitly generated, public, defaulted <code class="language-cpp">==</code> operator function. This leads to two questions:</p>
<ol>
<li>
<p>What happens if the explicitly defaulted <code class="language-cpp">&lt;=&gt;</code> operator function is private or protected? This question was brought to Evolution and the decision was that the implicitly generated <code class="language-cpp">==</code> operator should have the same access as the defaulted <code class="language-cpp">&lt;=&gt;</code> operator.</p>
</li>
<li>
<p>What happens if the explicitly defaulted <code class="language-cpp">&lt;=&gt;</code> operator is defined as deleted? There are three cases to consider here:</p>
<pre class="codehilite" data-line="21"><code class="language-cpp">struct Nothing { };
struct OnlyEq {
    bool operator==(OnlyEq const&amp;) const;
};
struct Weird {
    bool operator==(Weird const&amp;) const;
    auto operator&lt;=&gt;(Weird const&amp;) const = delete;
};

template &lt;typename T&gt;
struct wrapper {
    T t;
    auto operator&lt;=&gt;(wrapper const&amp;) const = default;
};

template &lt;typename T&gt;
bool operator==(T const&amp;, T const&amp;); // global, unconstrained candidate

template &lt;typename T&gt;
bool check(wrapper&lt;T&gt; const&amp; x) {
    return x == x; // (*)
}</code></pre>


</li>
</ol>
<p>The question is, what do <code class="language-cpp">check&lt;Nothing&gt;</code>, <code class="language-cpp">check&lt;OnlyEq&gt;</code>, and <code class="language-cpp">check&lt;Weird&gt;</code> do?</p>
<p>There are several choices that we could make here. A defaulted <code class="language-cpp">&lt;=&gt;</code> that is defined as deleted...</p>
<p>a) ... should still implicitly generate a defaulted <code class="language-cpp">==</code>. That defaulted <code class="language-cpp">==</code> could be defined as defaulted or deleted for its own rules.<br />
b) ... should <strong>not</strong> generate a defaulted <code class="language-cpp">==</code>.<br />
c) ... should generated an explicitly deleted <code class="language-cpp">==</code>.</p>
<p>Option (c) seems pointlessly user-hostile without much upside, so it will not be considered further. The meaning of the first two cases can be enumerated as follows:</p>
<table>
<tr>
<th />
<th>
<pre style="background:transparent;border:0px"><code class="language-cpp">check&lt;Nothing&gt;</code></pre>
</th>
<th>
<pre style="background:transparent;border:0px"><code class="language-cpp">check&lt;OnlyEq&gt;</code></pre>
</th>
<th>
<pre style="background:transparent;border:0px"><code class="language-cpp">check&lt;Weird&gt;</code></pre>
</th>
</tr>
<tr>
<th>
<p>Generate <code class="language-cpp">==</code></p>
</td>
<td>
<p>The generated <code class="language-cpp">==</code> would be defined as deleted because <code class="language-cpp">Nothing</code> has no <code class="language-cpp">==</code>.</p>
<p>As a result, <code class="language-cpp">check&lt;Nothing&gt;</code> is ill-formed.</p>
</td>
<td>
<p>The generated <code class="language-cpp">==</code> would be defined as defaulted, because <code class="language-cpp">OnlyEq</code> has an <code class="language-cpp">==</code>.</p>
<p><code class="language-cpp">check&lt;OnlyEq&gt;</code> is well-formed and goes through <code class="language-cpp">OnlyEq::operator==</code>.</p>
</td>
<td>
<p>The generated <code class="language-cpp">==</code> would be defined as defaulted, because <code class="language-cpp">Weird</code> does have an <code class="language-cpp">==</code> despite the deleted <code class="language-cpp">&lt;=&gt;</code>.</p>
<p><code class="language-cpp">check&lt;Weird&gt;</code> is well-formed and goes through <code class="language-cpp">Weird::operator==</code>.</p>
</td>
</tr>
<tr>
<th>
<p>Do Not Generate <code class="language-cpp">==</code></p>
</td>
<td colspan="3">
<p>There is no generated <code class="language-cpp">==</code>, so in all cases, the global candidate is invoked.</p>
</td>
</tr>
</table>

<p>In other words, there is a case (i.e. <code class="language-cpp">Nothing</code>) where option 1 ends up with a deleted <code class="language-cpp">==</code> instead of nothing and two cases (i.e. <code class="language-cpp">OnlyEq</code> and <code class="language-cpp">Weird</code>) where option 1 ends up with a valid and defaulted <code class="language-cpp">==</code> instead of nothing. </p>
<p>The question is: what is the intent of the class author of <code class="language-cpp">wrapper</code>? Arguably, the intent in this case is clear: just give me all the defaults. If we do not actually end up getting all the defaults (that is, <code class="language-cpp">wrapper&lt;OnlyEq&gt;</code> is not equality comparable), then the class author would have to write this regardless:</p>
<pre class="codehilite" data-line="4"><code class="language-cpp">template &lt;typename T&gt;
struct wrapper {
    T t;
    bool operator==(wrapper const&amp;) const = default;
    auto operator&lt;=&gt;(wrapper const&amp;) const = default;
};</code></pre>


<p>Just to ensure that we really do <em>get the defaults</em>. And at that point, we've basically obviated the feature. </p>
<p>The intent of the proposal that defaulting <code class="language-cpp">&lt;=&gt;</code> gets you defaulted <code class="language-cpp">==</code> is very much that defaulting <code class="language-cpp">&lt;=&gt;</code> really means also having declared defaulted <code class="language-cpp">==</code>. Option B does not get us there, and Option C definitely does not get us anywhere. I believe Option A is the clear choice here. </p>
<h2 id="wording">6. Wording<a class="self-link" href="#wording"></a></h2>
<p>Add a missing const to 10.10.1 [class.compare.default] paragraph 1, bullet 1:</p>
<blockquote>
<p>A defaulted comparison operator function ([expr.spaceship], [expr.rel], [expr.eq]) for some class <code class="language-cpp">C</code> shall be a non-template function declared in the member-specification of <code class="language-cpp">C</code> that is</p>
<ul>
<li>a non-static <ins>const</ins> member of <code class="language-cpp">C</code> having one parameter of type <code class="language-cpp">const C&amp;</code>, or</li>
<li>a friend of <code class="language-cpp">C</code> having two parameters of type <code class="language-cpp">const C&amp;</code>.</li>
</ul>
</blockquote>
<p>Add a new paragraph after 10.10.1 [class.compare.default] paragraph 1:</p>
<blockquote>
<p><ins>If the class definition does not explicitly declare an <code class="language-cpp">==</code> operator function, but declares a defaulted three-way comparison operator function, an <code class="language-cpp">==</code> operator function is declared implicitly with the same access as the three-way comparison operator function. The implicitly-declared <code class="language-cpp">==</code> operator for a class <code class="language-cpp">X</code> is an inline member of the form</ins>  </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<ins><code>bool X::operator==(const X&amp;) const</code></ins>  </p>
<p><ins>and is defined as defaulted in the definition of <code class="language-cpp">X</code>. The operator is a <code class="language-cpp">constexpr</code> function if its definition would satisfy the requirements for a <code class="language-cpp">constexpr</code> function. <i>[ Note: </i> the <code class="language-cpp">==</code> operator function is declared implicitly even if the defaulted three-way comparison operator function is defined as deleted. <i> - end note]</i></p>
</blockquote>
<p>Replace 10.10.1 [class.compare.default] paragraph 2:</p>
<blockquote>
<p><del>A three-way comparison operator for a class type <code class="language-cpp">C</code> is a <em>structural comparison operator</em> if it is defined as defaulted in the definition of <code class="language-cpp">C</code>, and all three-way comparison operators it invokes are structural comparison operators. A type <code class="language-cpp">T</code> has <em>strong structural equality</em> if, for a glvalue <code class="language-cpp">x</code> of type <code class="language-cpp">const T</code>, <code class="language-cpp">x &lt;=&gt; x</code> is a valid expression of type <code class="language-cpp">std​::​strong_ordering</code> or <code class="language-cpp">std​::​strong_equality</code> and either does not invoke a three-way comparison operator or invokes a structural comparison operator.</del></p>
</blockquote>
<p>with:</p>
<blockquote>
<p><ins>A type <code class="language-cpp">C</code> has <em>strong structural equality</em> if, given a glvalue <code class="language-cpp">x</code> of type <code class="language-cpp">const C</code>, either:</ins></p>
<ul>
<li><ins><code class="language-cpp">C</code> is a non-class type and <code class="language-cpp">x &lt;=&gt; x</code> is a valid expression of type <code class="language-cpp">std::strong_ordering</code> or <code class="language-cpp">std::strong_equality</code>, or</ins></li>
<li><ins><code class="language-cpp">C</code> is a class type with an <code class="language-cpp">==</code> operator defined as defaulted in the definition of <code class="language-cpp">C</code>, <code class="language-cpp">x == x</code> is well-formed when contextually converted to <code class="language-cpp">bool</code>, and all of <code class="language-cpp">C</code>'s base class subobjects and non-static data members have strong structural equality.</ins> </li>
</ul>
</blockquote>
<p>Move most of 10.10.2 [class.spaceship] paragraph 1 into a new paragraph at the end of 10.10.1 [class.compare.default]:</p>
<blockquote>
<p><ins>The direct base class subobjects of C, in the order of their declaration in the base-specifier-list of C, followed
by the non-static data members of C, in the order of their declaration in the member-specification 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 <code>x<sub>i</sub></code> be an lvalue denoting the i
th element in the expanded list of subobjects for an object x (of length n), where <code>x<sub>i</sub></code>
is formed by a sequence of derived-to-base conversions (11.3.3.1), class member access expressions (7.6.1.5), and array subscript expressions (7.6.1.1)
applied to x. It is unspecified whether virtual base class subobjects appear more than once in the expanded list of subobjects.</ins></p>
</blockquote>
<p>Before 10.10.2 [class.spaceship], insert a new subclause [class.eq] referring specifically to equality and inequality containing the following:</p>
<blockquote>
<p><ins>A defaulted equality operator (7.6.10) function shall have a declared return type <code class="language-cpp">bool</code>.</p>
<p><ins>A defaulted <code class="language-cpp">==</code> operator function for a class <code class="language-cpp">C</code> is defined as deleted unless, for each <code>x<sub>i</sub></code> in the expanded list of subobjects for an object <code class="language-cpp">x</code> of type <code class="language-cpp">C</code>, <code>x<sub>i</sub> == x<sub>i</sub></code> is a valid expression and contextually convertible to <code class="language-cpp">bool</code>.</p>
<p><ins>The return value <code class="language-cpp">V</code> of a defaulted <code class="language-cpp">==</code> operator function with parameters <code class="language-cpp">x</code> and <code class="language-cpp">y</code> is determined by comparing corresponding elements <code>x<sub>i</sub></code> and <code>y<sub>i</sub></code> in the expanded lists of subobjects for <code class="language-cpp">x</code> and <code class="language-cpp">y</code> until the first index <code class="language-cpp">i</code> where <code>x<sub>i</sub> == y<sub>i</sub></code> yields a result value which, when contextually converted to bool, yields <code class="language-cpp">false</code>. If no such index exists, <code class="language-cpp">V</code> is <code class="language-cpp">true</code>. Otherwise, <code class="language-cpp">V</code> is <code class="language-cpp">false</code>.</ins></p>
<p><ins>A defaulted <code class="language-cpp">!=</code> operator function for a class <code class="language-cpp">C</code> with parameters <code class="language-cpp">x</code> and <code class="language-cpp">y</code> is defined as deleted if</ins></p>
<ul>
<li><ins>overload resolution ([over.match]), as applied to <code class="language-cpp">x == y</code> (also considering synthesized candidates with reversed order of parameters ([over.match.oper])), results in an ambiguity or a function that is deleted or inaccessible from the operator function, or</ins></li>
<li><ins><code class="language-cpp">x == y</code> or <code class="language-cpp">y == x</code> cannot be contextually converted to <code class="language-cpp">bool</code>.</ins></li>
</ul>
<p><ins>Otherwise, the operator function yields <code class="language-cpp">(x == y) ? false : true</code> if an operator <code class="language-cpp">==</code> with the original order of parameters was selected, or <code class="language-cpp">(y == x) ? false : true</code> otherwise.</ins></p>
</blockquote>
<blockquote><ins><i>[Example -</i>
<pre><code>struct D {
  int i;
  friend bool operator==(const D& x, const D& y) = default; // OK, returns x.i == y.i
  bool operator!=(const D& z) const = default;              // OK, returns (*this == z) ? false : true
};</code></pre>

<i> - end example]</i></ins></blockquote>

<p>Remove all of 10.10.2 [class.spaceship] paragraph 1. Most of it was moved to 10.10.1, one sentence in it will be moved to the next paragraph:</p>
<blockquote>
<p><del>The direct base class subobjects of C, in the order of their declaration in the base-specifier-list of C, followed
by the non-static data members of C, in the order of their declaration in the member-specification 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 xi be an lvalue denoting the i
th element in the
expanded list of subobjects for an object x (of length n), where xi
is formed by a sequence of derived-to-base
conversions (11.3.3.1), class member access expressions (7.6.1.5), and array subscript expressions (7.6.1.1)
applied to x. The type of the expression <code>x<sub>i</sub> &lt;=&gt; x<sub>i</sub></code>
is denoted by <code>R<sub>i</sub></code>. It is unspecified whether virtual base class subobjects are compared more than once.</del></p>
</blockquote>
<p>Add a new sentence to the start of 10.10.2 [class.spaceship] paragraph 2 (which will become paragraph 1):</p>
<blockquote>
<p><ins>Given an expanded list of subobjects for an object <code class="language-cpp">x</code> of type <code class="language-cpp">C</code>, the type of the expression <code>x<sub>i</sub> &lt;=&gt; x<sub>i</sub></code>
is denoted by <code>R<sub>i</sub></code></ins>. If the declared return type of a defaulted three-way comparison operator function is <code class="language-cpp">auto</code>, then the return type is deduced as the common comparison type (see below) of <code>R<sub>0</sub>, R<sub>1</sub>, . . . , R<sub>n−1</sub></code>. [...]</p>
</blockquote>
<p>Rename clause "Other Comparison operators" [class.rel.eq] to "Relational Operators" [class.rel]. Remove the equality reference from 10.10.3 [class.rel.eq] paragraph 1:</p>
<blockquote>
<p>A defaulted relational (7.6.9) <del>or equality (7.6.10)</del> operator function for some operator @ shall have a declared
return type bool.</p>
</blockquote>
<p>Change the example in [class.rel.eq] paragraph 3:</p>
<blockquote><pre><code class="language-cpp">struct C {
  friend std::strong_equality operator<=>(const C&, const C&);
  </code><code><del>friend bool operator==(const C& x, const C& y) = default; // OK, returns x <=> y == 0</del></code><code class="language-cpp">
  bool operator<(const C&) = default;                       // OK, function is deleted  
};</code></pre></blockquote>

<p>Change 11.3.1.2 [over.match.oper] paragraph 3.4:</p>
<blockquote>
<ul>
<li>[...]</li>
<li>For the relational ([expr.rel]) <del>and equality ([expr.eq])</del> operators, the rewritten candidates include all member, non-member, and built-in candidates for the operator <code class="language-cpp">&lt;=&gt;</code> for which the rewritten expression <code class="language-cpp">(x &lt;=&gt; y) @ 0</code> is well-formed using that operator <code class="language-cpp">&lt;=&gt;</code>. For the relational ([expr.rel])<del>, equality ([expr.eq]),</del> and three-way comparison ([expr.spaceship]) operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each member, non-member, and built-in candidate for the operator &lt;=&gt; for which the rewritten expression 0 @ (y &lt;=&gt; x) is well-formed using that operator&lt;=&gt;.  <ins>For the <code class="language-cpp">!=</code> operator ([expr.eq]), the rewritten candidates include all member, non-member, and built-in candidates for the operator <code class="language-cpp">==</code> for which the rewritten expression <code class="language-cpp">(x == y)</code> is well-formed when contextually converted to <code class="language-cpp">bool</code> using that operator <code class="language-cpp">==</code>. For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each member, non-member, and built-in candidate for the operator <code class="language-cpp">==</code> for which the rewritten expression <code class="language-cpp">(y == x)</code> is well-formed when contextually converted to <code class="language-cpp">bool</code> using that operator <code class="language-cpp">==</code>.</ins> <em>[ Note:</em> A candidate synthesized from a member candidate has its implicit object parameter as the second parameter, thus implicit conversions are considered for the first, but not for the second, parameter. <em>—end note]</em> In each case, rewritten candidates are not considered in the context of the rewritten expression. For all other operators, the rewritten candidate set is empty.</li>
</ul>
</blockquote>
<p>Change 11.3.1.2 [over.match.oper] paragraph 8:</p>
<blockquote>
<p>If a rewritten candidate is selected by overload resolution for <del>an</del> <ins>a relational or three-way comparison</ins> operator <code class="language-cpp">@</code>, <code class="language-cpp">x @ y</code> is interpreted as the rewritten expression: <code class="language-cpp">0 @ (y &lt;=&gt; x)</code> if the selected candidate is a synthesized candidate with reversed order
of parameters, or <code class="language-cpp">(x &lt;=&gt; y) @ 0</code> otherwise, using the selected rewritten <code class="language-cpp">operator&lt;=&gt;</code> candidate. <ins>If a rewritten candidate is selected by overload resolution for a <code class="language-cpp">!=</code> operator, <code class="language-cpp">x != y</code> is interpreted as <code class="language-cpp">(y == x) ? false : true</code> if the selected candidate is a synthesized candidate with reversed order of parameters, or <code class="language-cpp">(x == y) ? false : true</code> otherwise, using the selected rewritten <code class="language-cpp">operator==</code> candidate. If a rewritten candidate is selected by overload resolution for an <code class="language-cpp">==</code> operator, <code class="language-cpp">x == y</code> is interpreted as <code class="language-cpp">(y == x) ? true : false</code> using the selected rewritten <code class="language-cpp">operator==</code> candidate.</ins></p>
</blockquote>
<p>Change 12.1 [temp.param]/4 to refer to <code class="language-cpp">==</code> instead of <code class="language-cpp">&lt;=&gt;</code>:</p>
<blockquote>
<p>a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member <del><code>operator&lt;=&gt;</code></del> <ins><code>operator==</code></ins>, then it is declared public,</p>
</blockquote>
<p>Change the example in 12.1 [temp.param]/p6 to default <code class="language-cpp">==</code> instead of <code class="language-cpp">&lt;=&gt;</code>.</p>
<blockquote><pre><code>struct A { friend auto <del>operator&lt;=&gt;</del> <ins>operator==</ins>(const A&, const A&) = default; };</code></pre></blockquote>

<p>Change the example in 12.3.2 [temp.arg.nontype]/p4 to default <code class="language-cpp">==</code> instead of <code class="language-cpp">&lt;=&gt;</code> (and additionally fix its arity):</p>
<blockquote><pre><code>auto <del>operator&lt;=&gt;(A, A)</del> <ins>operator==(const A&) const</ins> = default;</code></pre></blockquote>

<p>Change 12.5 [temp.type] to refer to <code class="language-cpp">==</code> instead of <code class="language-cpp">&lt;=&gt;</code>:</p>
<blockquote>
<ul>
<li>their remaining corresponding non-type template-arguments have the same type and value after conversion to the type of the template-parameter, where they are considered to have the same value if they compare equal with <del><code>operator&lt;=&gt;</code></del> <ins><code>operator==</code></ins>, and</li>
</ul>
</blockquote>
<h2 id="acknowledgements">7. Acknowledgements<a class="self-link" href="#acknowledgements"></a></h2>
<p>This paper most certainly would not exist without David Stone's extensive work in this area. Thanks also to Agustín Bergé for discussing issues with me. Thanks to Jens Maurer for extensive wording help.</p>
<h2 id="references">8. References<a class="self-link" href="#references"></a></h2><ul><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r3.pdf">[P0515R3]</a><span style="margin-left: 5px;">"Consistent comparison" by Herb Sutter, Jens Maurer, Walter E. Brown, 2017-11-10</span></li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0732r2.pdf">[P0732R2]</a><span style="margin-left: 5px;">"Class Types in Non-Type Template Parameters" by Jeff Snyder, Louis Dionne, 2018-06-06</span></li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1190r0.html">[P1190R0]</a><span style="margin-left: 5px;">"I did not order this! Why is it on my bill?" by David Stone, 2018-08-06</span></li><li><a href="http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Eq.html">[haskell.eq]</a><span style="margin-left: 5px;">Data.Eq - Haskell documentation</span></li><li><a href="http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Ord.html">[haskell.ord]</a><span style="margin-left: 5px;">Data.Ord - Haskell documentation</span></li><li><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html">[kotlin.any]</a><span style="margin-left: 5px;">Any - Kotlin Programming Language</span></li><li><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-comparable/index.html">[kotlin.comp]</a><span style="margin-left: 5px;">Comparable - Kotlin Programming Language</span></li><li><a href="https://kotlinlang.org/docs/reference/operator-overloading.html#equals">[kotlin.oper]</a><span style="margin-left: 5px;">Operator overloading - Kotlin Programming Language</span></li><li><a href="https://medium.com/@barryrevzin/implementing-the-spaceship-operator-for-optional-4de89fc6d5ec">[revzin.impl]</a><span style="margin-left: 5px;">"Implementing the spaceship operator for optional" by Barry Revzin, 2017-11-16</span></li><li><a href="https://doc.rust-lang.org/src/core/slice/mod.rs.html?search=#4037-4053">[rust.eq]</a><span style="margin-left: 5px;">Implementation of Eq for Slice</span></li><li><a href="https://doc.rust-lang.org/reference/expressions/operator-expr.html#comparison-operators">[rust.oper]</a><span style="margin-left: 5px;">Comparison Operators - The Rust Reference</span></li><li><a href="https://doc.rust-lang.org/src/core/slice/mod.rs.html#4116-4136">[rust.ord]</a><span style="margin-left: 5px;">Implementation of Ord for Slice</span></li><li><a href="https://www.scala-lang.org/api/current/scala/Any.html#==(x$1:Any):Boolean">[scala.any]</a><span style="margin-left: 5px;">Scala Standard Library - Any</span></li><li><a href="https://www.scala-lang.org/api/current/scala/math/Ordered.html">[scala.ord]</a><span style="margin-left: 5px;">Scala Standard Library - Ordered</span></li><li><a href="http://stepanovpapers.com/DeSt98.pdf">[stepanov.fogp]</a><span style="margin-left: 5px;">"Fundamentals of Generic Programming" by James C. Dehnert and Alexander Stepanov, 1998</span></li><li><a href="https://developer.apple.com/documentation/swift/comparable">[swift.comp]</a><span style="margin-left: 5px;">Comparable - Swift Standard Library</span></li><li><a href="https://developer.apple.com/documentation/swift/equatable">[swift.eq]</a><span style="margin-left: 5px;">Equatable - Swift Standard Library</span></li></ul>
</html>