<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>When do you actually use <=>?</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: P1186R1 <br />
Date: 2019-01-22 <br />
Audience: EWG <br />
Reply-To: Barry Revzin, barry dot revzin at gmail dot com <br />
</address>
<hr /><h1 align=center><p>When do you actually use <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="#an-adoption-story">An Adoption Story</a></li>
<li><a href="#the-case-against-automatic-synthesis">The Case Against Automatic Synthesis</a></li>
<li><a href="#an-adoption-story-for-templates">An Adoption Story for Templates</a></li>
<li><a href="#status-quo">Status Quo</a></li>
</ol>
</li>
<li><a href="#proposal">Proposal</a><ol>
<li><a href="#soundness-of-synthesis">Soundness of Synthesis</a></li>
<li><a href="#explanatory-examples">Explanatory Examples</a></li>
<li><a href="#differences-from-status-quo-and-p1186r0">Differences from Status Quo and P1186R0</a></li>
<li><a href="#building-complexity">Building complexity</a></li>
<li><a href="#what-about-compare_3way">What about compare_3way()?</a></li>
<li><a href="#what-about-xxx_equality">What about XXX_equality?</a></li>
</ol>
</li>
<li><a href="#wording">Wording</a></li>
<li><a href="#acknowledgments">Acknowledgments</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><a href="https://wg21.link/p1186r0" title="When do you actually use &lt;=&gt;?">R0</a> of this paper was approved by both EWG and LEWG. Under Core review, the issue of <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1186r0.html#unintentional-comparison-category-strengthening">unintentional comparison category strengthening</a> was brought up as a reason to strongly oppose the design. As a result, this revision proposes a different way to solve the issues presented in R0.</p>
<p>The library portion of R0 was moved into <a href="https://wg21.link/p1188r0" title="Library utilities for &lt;=&gt;">P1188R0</a>. This paper is <em>solely</em> a proposal for language change.</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 a result of <a href="https://wg21.link/p1185r0" title="&lt;=&gt; != ==">P1185R0</a>, that has become two functions, but importantly you still only need to declare one operator function to generate each of the four relational comparison operators.</p>
<p>In a future world, where all types have adopted <code class="language-cpp">&lt;=&gt;</code>, this will work great. It will be very easy to implement <code class="language-cpp">&lt;=&gt;</code> for a type like <code class="language-cpp">optional&lt;T&gt;</code> (writing as a non-member function for clarity):</p>
<pre class="codehilite"><code class="language-cpp">template &lt;typename T&gt;
compare_3way_type_t&lt;T&gt; // see P1188
operator&lt;=&gt;(optional&lt;T&gt; const&amp; lhs, optional&lt;T&gt; const&amp; rhs)
{
    if (lhs.has_value() &amp;&amp; rhs.has_value()) {
        return *lhs &lt;=&gt; *rhs;
    } else {
        return lhs.has_value() &lt;=&gt; rhs.has_value();
    }
}</code></pre>


<p>This is a clean and elegant way of implementing this functionality, and gives us <code class="language-cpp">&lt;</code>, <code class="language-cpp">&gt;</code>, <code class="language-cpp">&lt;=</code>, and <code class="language-cpp">&gt;=</code> that all do the right thing. What about <code class="language-cpp">vector&lt;T&gt;</code>?</p>
<pre class="codehilite"><code class="language-cpp">template &lt;typename T&gt;
compare_3way_type_t&lt;T&gt;
operator&lt;=&gt;(vector&lt;T&gt; const&amp; lhs, vector&lt;T&gt; const&amp; rhs)
{
    return lexicographical_compare_3way(
        lhs.begin(), lhs.end(),
        rhs.begin(), rhs.end());
}</code></pre>


<p>Even better.</p>
<p>What about a simple aggregate type, where all we want is to do normal member-by-member lexicographical comparison? No problem:</p>
<pre class="codehilite"><code class="language-cpp">struct Aggr {
    X x;
    Y y;
    Z z;

    auto operator&lt;=&gt;(Aggr const&amp;) const = default;
};</code></pre>


<p>Beautiful.</p>
<h3 id="an-adoption-story">2.1. An Adoption Story<a class="self-link" href="#an-adoption-story"></a></h3>
<p>The problem is that we're not in this future world quite yet. No program-defined types have <code class="language-cpp">&lt;=&gt;</code>, the only standard library type that has <code class="language-cpp">&lt;=&gt;</code> so far is <code class="language-cpp">nullptr_t</code>. Which means we can't just replace the existing relational operators from <code class="language-cpp">optional&lt;T&gt;</code> and <code class="language-cpp">vector&lt;T&gt;</code> with <code class="language-cpp">&lt;=&gt;</code> and probably won't be able to just default <code class="language-cpp">Aggr</code>'s <code class="language-cpp">&lt;=&gt;</code>. We need to do something more involved.</p>
<p>How do we implement <code class="language-cpp">&lt;=&gt;</code> for a type that looks like this:</p>
<pre class="codehilite"><code class="language-cpp">// not in our immedate control
struct Legacy {
    bool operator==(Legacy const&amp;) const;
    bool operator&lt;(Legacy const&amp;) const;
};

// trying to write/update this type
struct Aggr {
    int i;
    char c;
    Legacy q;

    // ok, easy, thanks to P1185
    bool operator==(Aggr const&amp;) const = default;

    // ... but not this
    auto operator&lt;=&gt;(Aggr const&amp;) const = default;
};</code></pre>


<p>The implementation of <code class="language-cpp">&lt;=&gt;</code> won't work for <code class="language-cpp">Aggr</code>. <code class="language-cpp">Legacy</code> doesn't have a <code class="language-cpp">&lt;=&gt;</code>, so our spaceship operator ends up being defined as deleted. We don't get the "free" memberwise comparison from just defaulting. Right now, we have to write it by hand:</p>
<pre class="codehilite"><code class="language-cpp">strong_ordering operator&lt;=&gt;(Aggr const&amp; rhs) const
{
    if (auto cmp = i &lt;=&gt; rhs.i; cmp != 0) return cmp;
    if (auto cmp = c &lt;=&gt; rhs.c; cmp != 0) return cmp;

    if (q == rhs.q) return strong_ordering::equal;
    if (q &lt; rhs.q) return strong_ordering::less;
    return strong_ordering::greater;
}</code></pre>


<p>Such an implementation would always give us a correct answer, but it's not actually a good implementation. At some point, <code class="language-cpp">Legacy</code> is going to adopt <code class="language-cpp">&lt;=&gt;</code> and we really need to plan in advance for that scenario; we definitely want to use <code class="language-cpp">&lt;=&gt;</code> whenever it's available.</p>
<p>It would be better to write:</p>
<pre class="codehilite"><code class="language-cpp">strong_ordering operator&lt;=&gt;(Aggr const&amp; rhs) const
{
    if (auto cmp = i &lt;=&gt; rhs.i; cmp != 0) return cmp;
    if (auto cmp = c &lt;=&gt; rhs.c; cmp != 0) return cmp;
    return compare_3way(q, rhs.q);
}</code></pre>


<p>It's at this point that R0 went onto suggest that because <code class="language-cpp">compare_3way()</code> is transparent to <code class="language-cpp">&lt;=&gt;</code>, you may as well just always use <code class="language-cpp">compare_3way()</code> and then you may as well just define <code class="language-cpp">&lt;=&gt;</code> to be that exact logic. That language change would allow us to just <code class="language-cpp">= default</code> the spaceship operator for types like <code class="language-cpp">Aggr</code>.</p>
<pre class="codehilite"><code class="language-cpp">// P1186R0, this involves just synthesizing an &lt;=&gt; for Legacy
auto operator&lt;=&gt;(Aggr const&amp;) const = default;</code></pre>


<h3 id="the-case-against-automatic-synthesis">2.2. The Case Against Automatic Synthesis<a class="self-link" href="#the-case-against-automatic-synthesis"></a></h3>
<p>Consider the following legacy type:</p>
<pre class="codehilite"><code class="language-cpp">struct Q {
    float f;
    bool operator==(Q rhs) const { return f == rhs.f; }
    bool operator&lt;(Q rhs) const { return f &lt; rhs.f; }
    bool operator&gt;(Q rhs) const { return f &gt; rhs.f; }
};</code></pre>


<p>Using <code class="language-cpp">float</code> just makes for a short example, but the salient point here is that <code class="language-cpp">Q</code>'s ordering is partial, not total. The significance of partial orders is that these can all be <code class="language-cpp">false</code>:</p>
<pre class="codehilite"><code class="language-cpp">Q{1.0f} == Q{NAN}; // false
Q{1.0f} &lt; Q{NAN};  // false
Q{1.0f} &gt; Q{NAN};  // false</code></pre>


<p>However, the proposed synthesis rules in P1186R0 would have led (with no source code changes!) to the following:</p>
<pre class="codehilite"><code class="language-cpp">Q{1.0f} &gt; Q{NAN};       // false
Q{1.0f} &lt;=&gt; Q{NAN} &gt; 0; // true</code></pre>


<p>This is because the proposed rules assumed a total order, wherein <code class="language-cpp">!(a == b) &amp;&amp; !(a &lt; b)</code> imply <code class="language-cpp">a &gt; b</code>.</p>
<p>Now, you might ask... why don't we just synthesize a <em>partial</em> ordering instead of a <em>total</em> ordering? Wouldn't we get it correct in that situation? Yes, we would. But synthesizing a partial order requires an extra comparison:</p>
<pre class="codehilite"><code class="language-cpp">friend partial_ordering operator&lt;=&gt;(Q const&amp; a, Q const&amp; b)
{
    if (a == b) return partial_ordering::equivalent;
    if (a &lt; b)  return partial_ordering::less;
    if (b &lt; a)  return partial_ordering::greater;
    return partial_ordering::unordered;
}</code></pre>


<p>Many types which do not provide <code class="language-cpp">&lt;=&gt;</code> do still implement a total order. While assuming a partial order is completely safe and correct (we might say <code class="language-cpp">equivalent</code> when it really should be <code class="language-cpp">equal</code>, but at least we won't ever say <code class="language-cpp">greater</code> when it really should be <code class="language-cpp">unordered</code>!), for many types that's a performance burden. For totally ordered types, that last comparison is unnecessary - since by definition there is no case where we return <code class="language-cpp">unordered</code>. It would be unfortunate to adopt a language feature as purely a convenience feature to ease adoption of <code class="language-cpp">&lt;=&gt;</code>, but end up with a feature that many will eschew and hand-write their own comparisons - possibly incorrectly.</p>
<p>The goal of this proposal is to try to have our cake an eat it too:</p>
<ul>
<li>allow types like <code class="language-cpp">Aggr</code> which just want the simple, default, member-wise comparisons to express that with as little typing as possible</li>
<li>ensure that we do not provide incorrect answers to comparison queries</li>
<li>ensure that such a feature does not impose overhead over the handwritten equivalent</li>
</ul>
<p>The first bullet implies the need for <em>some</em> language change. The second bullet kills P1186R0, the third bullet kills a variant of P1186R0 that would synthesize <code class="language-cpp">partial_ordering</code> instead of <code class="language-cpp">strong_ordering</code>, and the two taken together basically ensure that we cannot have a language feature that synthesis <code class="language-cpp">&lt;=&gt;</code> for a type with opt-in.</p>
<h3 id="an-adoption-story-for-templates">2.3. An Adoption Story for Templates<a class="self-link" href="#an-adoption-story-for-templates"></a></h3>
<p>Taking a step to the side to talk about an adoption story for class templates. How would <code class="language-cpp">vector&lt;T&gt;</code> and <code class="language-cpp">optional&lt;T&gt;</code> and similar containers and templates adopt <code class="language-cpp">operator&lt;=&gt;</code>? </p>
<p>R0 of this paper <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1186r0.html#the-initial-premise-is-false-optionalt-shouldnt-always-have">argued</a> against the claim that "[a]ny compound type should have <code class="language-cpp">&lt;=&gt;</code> only if all of its constituents have <code class="language-cpp">&lt;=&gt;</code>." At the time, my understanding of what "conditional spaceship" meant was this:</p>
<pre class="codehilite"><code class="language-cpp">// to handle legacy types. This is called Cpp17LessThanComparable in the
// working draft
template &lt;typename T&gt;
concept HasLess = requires (remove_reference_t&lt;T&gt; const&amp; t) {
    { t &lt; t } -&gt; bool
};

template &lt;HasLess T&gt;
bool operator&lt;(vector&lt;T&gt; const&amp;, vector&lt;T&gt; const&amp;);

template &lt;ThreeWayComparable T&gt; // see P1188
compare_3way_type_t operator&lt;=&gt;(vector&lt;T&gt; const&amp;, vector&lt;T&gt; const&amp;);</code></pre>


<p>This is, indeed, a bad implementation strategy because <code class="language-cpp">v1 &lt; v2</code> would invoke <code class="language-cpp">operator&lt;</code> even if <code class="language-cpp">operator&lt;=&gt;</code> was a viable option, so we lose the potential performance benefit. It's quite important to ensure that we use <code class="language-cpp">&lt;=&gt;</code> if that's at all an option. It's this problem that partially led to my writing P1186R0.</p>
<p>But since I wrote this paper, I've come up with a much better way of <a href="https://brevzin.github.io/c++/2018/12/21/spaceship-for-vector/" title="Conditionally implementing spaceship">conditionally adopting spaceship</a>:</p>
<pre class="codehilite"><code class="language-cpp">template &lt;HasLess T&gt;
bool operator&lt;(vector&lt;T&gt; const&amp;, vector&lt;T&gt; const&amp;);

template &lt;ThreeWayComparable T&gt; requires HasLess&lt;T&gt;
compare_3way_type_t operator&lt;=&gt;(vector&lt;T&gt; const&amp;, vector&lt;T&gt; const&amp;);</code></pre>


<p>It's a small, seemingly redundant change (after all, if <code class="language-cpp">ThreeWayComparable&lt;T&gt;</code> then surely <code class="language-cpp">HasLess&lt;T&gt;</code> for all types other than pathologically absurd ones that provide <code class="language-cpp">&lt;=&gt;</code> but explicitly delete <code class="language-cpp">&lt;</code>), but it ensures that <code class="language-cpp">v1 &lt; v2</code> invokes <code class="language-cpp">operator&lt;=&gt;</code> where possible. </p>
<p>Conditionally adopting spaceship between C++17 and C++20 is actually even easier:</p>
<pre class="codehilite"><code class="language-cpp">template &lt;typename T&gt;
enable_if_t&lt;supports_lt&lt;T&gt;::value, bool&gt; // normal C++17 SFINAE machinery
operator&lt;(vector&lt;T&gt; const&amp;, vector&lt;T&gt; const&amp;);

// use the feature-test macro for operator&lt;=&gt;
#if __cpp_impl_three_way_comparison
template &lt;ThreeWayComparable T&gt;
compare_3way_type_t&lt;T&gt; operator&lt;=&gt;(vector&lt;T&gt; const&amp;, vector&lt;T&gt; const&amp;);
#endif</code></pre>


<p>In short, conditionally adopting <code class="language-cpp">&lt;=&gt;</code> has a good user story, once you know how to do it. This is very doable, and is no longer, if of itself, a motivation for making a language change. It is, however, a motivation for <em>not</em> synthesizing <code class="language-cpp">&lt;=&gt;</code> in a way that leads to incorrect answers or poor performance - as this would have far-reaching effects.</p>
<p>The above is solely about the case where we want to adopt <code class="language-cpp">&lt;=&gt;</code> <em>conditionally</em>. If we want to adopt <code class="language-cpp">&lt;=&gt;</code> <em>unconditionally</em>, we'll need to do the same kind of things in the template case as we want to do in the non-template case. We need some way of invoking <code class="language-cpp">&lt;=&gt;</code> where possible, but falling back to a synthesized three-way comparison from the two-way comparison operators. </p>
<h3 id="status-quo">2.4. Status Quo<a class="self-link" href="#status-quo"></a></h3>
<p>To be perfectly clear, the current rule for defaulting <code class="language-cpp">operator&lt;=&gt;</code> for a class <code class="language-cpp">C</code> is roughly as follows:</p>
<ul>
<li>For two objects <code class="language-cpp">x</code> and <code class="language-cpp">y</code> of type <code class="language-cpp">const C</code>, we compare their corresponding subobjects <code>x<sub>i</sub></code> and <code>y<sub>i</sub></code> until the first <em>i</em> where given <code>auto v<sub>i</sub> = x<sub>i</sub> &lt;=&gt; y<sub>i</sub></code>, <code>v<sub>i</sub> != 0</code>. If such an <em>i</em> exists, we return <code>v<sub>i</sub></code>. Else, we return <code class="language-cpp">strong_ordering::equal</code>.</li>
<li>If the return type of defaulted <code class="language-cpp">operator&lt;=&gt;</code> is <code class="language-cpp">auto</code>, we determine the return type by taking the common comparison category of all of the <code>x<sub>i</sub> &lt;=&gt; y<sub>i</sub></code> expressions. If the return type is provided, we ensure that it is valid. If any of the pairwise comparisons is ill-formed, or are not compatible with the provided return type, the defaulted <code class="language-cpp">operator&lt;=&gt;</code> is defined as deleted.</li>
</ul>
<p>In other words, for the <code class="language-cpp">Aggr</code> example, the declaration <code class="language-cpp">strong_ordering operator&lt;=&gt;(Aggr const&amp;) const = default;</code> expands into something like</p>
<pre class="codehilite" data-line="9"><code class="language-cpp">struct Aggr {
    int i;
    char c;
    Legacy q;

    strong_ordering operator&lt;=&gt;(Aggr const&amp; rhs) const {
        if (auto cmp = i &lt;=&gt; rhs.i; cmp != 0) return cmp;
        if (auto cmp = c &lt;=&gt; rhs.c; cmp != 0) return cmp;
        if (auto cmp = q &lt;=&gt; rhs.q; cmp != 0) return cmp;
        return strong_ordering::equal
    }
};</code></pre>


<p>Or it would, if the highlighted line were valid. <code class="language-cpp">Legacy</code> has no <code class="language-cpp">&lt;=&gt;</code>, so that pairwise comparison is ill-formed, so the operator function would be defined as deleted. </p>
<h2 id="proposal">3. Proposal<a class="self-link" href="#proposal"></a></h2>
<p>This paper proposes a new direction for a stop-gap adoption measure for <code class="language-cpp">operator&lt;=&gt;</code>: we will synthesize an <code class="language-cpp">operator&lt;=&gt;</code> for a type, but <em>only under very specific conditions</em>, and only when the user provides the comparison category that the comparison needs to use. All we need is a very narrow ability to help with <code class="language-cpp">&lt;=&gt;</code> adoption. This is that narrow ability.</p>
<p>Currently, the pairwise comparison of the subobjects is always <code>x<sub>i</sub> &lt;=&gt; y<sub>i</sub></code>. Always <code class="language-cpp">operator&lt;=&gt;</code>.</p>
<p>This paper proposes defining a new magic specification-only function <code><i>3WAY</i>&lt;R&gt;(a, b)</code>, which only has meaning in the context of defining what a defaulted <code class="language-cpp">operator&lt;=&gt;</code> does. The function definition is very wordy, but it's not actually complicated: we will use the provided return type to synthesize an appropriate ordering. The key points are:</p>
<ul>
<li>We will <em>only</em> synthesize an ordering if the user provides an explicit return type. We do not synthesize any ordering when the declared return type is <code class="language-cpp">auto</code>.</li>
<li>The presence of <code class="language-cpp">&lt;=&gt;</code> is <em>always</em> preferred to any kind of synthetic fallback. </li>
<li>Synthesizing a <code class="language-cpp">strong_ordering</code> requires both <code class="language-cpp">==</code> and <code class="language-cpp">&lt;</code>.</li>
<li>Synthesizing a <code class="language-cpp">weak_ordering</code> can use either <code class="language-cpp">==</code> and <code class="language-cpp">&lt;</code> or just <code class="language-cpp">&lt;</code>.</li>
<li>Synthesizing a <code class="language-cpp">partial_ordering</code> requires both <code class="language-cpp">==</code> and <code class="language-cpp">&lt;</code> and will do up to three comparisons. Those three comparisons are necessary for correctness. Any fewer comparisons would not be sound.</li>
<li>Synthesizing either <code class="language-cpp">strong_equality</code> or <code class="language-cpp">weak_equality</code> requires <code class="language-cpp">==</code>.</li>
</ul>
<p>We then change the meaning of defaulted <code class="language-cpp">operator&lt;=&gt;</code> to be defined in terms of this magic <code><i>3WAY</i>&lt;R&gt;(x<sub>i</sub>, y<sub>i</sub>)</code> function (see <a href="#3way-def">wording</a>) instead of in terms of <code>x<sub>i</sub> &lt;=&gt; y<sub>i</sub></code>. If <code><i>3WAY</i>&lt;R&gt;(a, b)</code> uses an expression without checking for it and that expression is ill-formed, the function is defined as deleted.</p>
<h3 id="soundness-of-synthesis">3.1. Soundness of Synthesis<a class="self-link" href="#soundness-of-synthesis"></a></h3>
<p>It would be sound to synthesize <code class="language-cpp">strong_ordering</code> from just performing <code class="language-cpp">&lt;</code> both ways, but equality is the salient difference between <code class="language-cpp">weak_ordering</code> and <code class="language-cpp">strong_ordering</code> and it doesn't seem right to synthesize a <code class="language-cpp">strong_ordering</code> from a type that doesn't even provide an <code class="language-cpp">==</code>.</p>
<p>There is no other sound way to synthesize <code class="language-cpp">partial_ordering</code> from <code class="language-cpp">==</code> and <code class="language-cpp">&lt;</code>. If we just do <code class="language-cpp">&lt;</code> both ways, we'd have to decide between <code class="language-cpp">equivalent</code> and <code class="language-cpp">unordered</code> in the case where <code class="language-cpp">!(a &lt; b) &amp;&amp; !(b &lt; a)</code> - the former gets the unordered cases wrong and the latter means our order isn't reflexive..</p>
<h3 id="explanatory-examples">3.2. Explanatory Examples<a class="self-link" href="#explanatory-examples"></a></h3>
<p>This might make more sense with examples.</p>
<table style="width:100%">
<tr>
<th style="width:50%">
<p>Source Code</p>
</th>
<th style="width:50%">
<p>Meaning</p>
</th>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct Aggr {
    int i;
    char c;
    Legacy q;

    auto operator&lt;=&gt;(Aggr const&amp;) const = default;
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct Aggr {
    int i;
    char c;
    Legacy q;

    // x.q &lt;=&gt; y.q is ill-formed and we have no return type
    // to guide our synthesis. Hence, deleted
    auto operator&lt;=&gt;(Aggr const&amp;) const = delete;
};</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct Aggr {
    int i;
    char c;
    Legacy q;

    strong_ordering operator&lt;=&gt;(Aggr const&amp;) const = default;
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct Aggr {
    int i;
    char c;
    Legacy q;

    strong_ordering operator&lt;=&gt;(Aggr const&amp; rhs) const {
        if (auto cmp = i &lt;=&gt; rhs.i; cmp != 0) return cmp;
        if (auto cmp = c &lt;=&gt; rhs.c; cmp != 0) return cmp;

        // synthesizing strong_ordering from == and &lt;
        if (q == rhs.q) return strong_ordering::equal;
        if (q &lt; rhs.q) return strong_ordering::less;

        // sanitizers might also check for
        [[ assert: rhs.q &lt; q; ]]
        return strong_ordering::greater;
    }
};</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct X {
    bool operator&lt;(X const&amp;) const;
};

struct Y {
    X x;

    strong_ordering operator&lt;=&gt;(Y const&amp;) const = default;
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct X {
    bool operator&lt;(X const&amp;) const;
};

struct Y {
    X x;

    // defined as deleted because X has no &lt;=&gt;, so we fallback
    // to synthesizing from == and &lt;, but we have no ==.
    strong_ordering operator&lt;=&gt;(Y const&amp;) const = delete;
};</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct W {
    weak_ordering operator&lt;=&gt;(W const&amp;) const;
};

struct Z {
    W w;
    Legacy q;

    strong_ordering operator&lt;=&gt;(Z const&amp;) const = default;
};</code></pre>
</td>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct W {
    weak_ordering operator&lt;=&gt;(W const&amp;) const;
};

struct Z {
    W w;
    Legacy q;

    // strong_ordering as a return type is not compatible with
    // W's comparison category, which is weak_ordering. Hence
    // defined as deleted
    strong_ordering operator&lt;=&gt;(Z const&amp;) const = delete;
};</code></pre>
</td>
</tr>
<tr>
<td>
<pre style="background:transparent;border:0px"><code class="language-cpp">struct W {
    weak_ordering operator&lt;=&gt;(W const&amp;) const;
};

struct Q {
    bool operator&lt;(Q const&amp;) const;
};

struct Z {
    W w;
    Q q;

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

struct Q {
    bool operator&lt;(Q const&amp;) const;
};

struct Z {
    W w;
    Q q;

    weak_ordering operator&lt;=&gt;(Z const&amp; rhs) const
    {
        if (auto cmp = w &lt;=&gt; rhs.w; cmp != 0) return cmp;

        // synthesizing weak_ordering from JUST &lt;
        if (q &lt; rhs.q) return weak_ordering::less;
        if (rhs.q &lt; q) return weak_ordering::greater;
        return weak_ordering::equivalent;
    }
};</code></pre>
</td>
</tr>
</table>

<h3 id="differences-from-status-quo-and-p1186r0">3.3. Differences from Status Quo and P1186R0<a class="self-link" href="#differences-from-status-quo-and-p1186r0"></a></h3>
<p>Consider the highlighted lines in the following example:</p>
<pre class="codehilite" data-line="6,10,15"><code class="language-cpp">struct Q {
    bool operator==(Q const&amp;) const;
    bool operator&lt;(Q const&amp;) const;
};

Q{} &lt;=&gt; Q{}; // #1

struct X {
    Q q;
    auto operator&lt;=&gt;(X const&amp;) const = default; // #2
};

struct Y {
    Q q;
    strong_ordering operator&lt;=&gt;(Y const&amp;) const = default; // #3
};</code></pre>


<p>In the working draft, <code class="language-cpp">#1</code> is ill-formed and <code class="language-cpp">#2</code> and <code class="language-cpp">#3</code> are both defined as deleted because <code class="language-cpp">Q</code> has no <code class="language-cpp">&lt;=&gt;</code>.</p>
<p>With P1186R0, <code class="language-cpp">#1</code> is a valid expression of type <code class="language-cpp">std::strong_ordering</code>, and <code class="language-cpp">#2</code> and <code class="language-cpp">#3</code> are both defined as defaulted. In all cases, synthesizing a strong comparison.</p>
<p>With this proposal, <code class="language-cpp">#1</code> is <em>still</em> ill-formed. <code class="language-cpp">#2</code> is defined as deleted, because <code class="language-cpp">Q</code> still has no <code class="language-cpp">&lt;=&gt;</code>. The only change is that in the case of <code class="language-cpp">#3</code>, because we know the user wants <code class="language-cpp">strong_ordering</code>, we provide one.</p>
<h3 id="building-complexity">3.4. Building complexity<a class="self-link" href="#building-complexity"></a></h3>
<p>The proposal here <em>only</em> applies to the specific case where we are defaulting <code class="language-cpp">operator&lt;=&gt;</code> and provide the comparison category that we want to default to. That might seem inherently limiting, but we can build up quite a lot from there.</p>
<p>Consider <code class="language-cpp">std::pair&lt;T, U&gt;</code>. Today, its <code class="language-cpp">operator&lt;=</code> is defined in terms of its <code class="language-cpp">operator&lt;</code>, which assumes a weak ordering. One thing we could do (which this paper is not proposing, this is just a thought experiment) is to synthesize <code class="language-cpp">&lt;=&gt;</code> with weak ordering as a fallback. </p>
<p>We do that with just a simple helper trait (which this paper is also not proposing):</p>
<pre class="codehilite"><code class="language-cpp">// use whatever &lt;=&gt; does, or pick weak_ordering
template &lt;typename T, typename C&gt;
using fallback_to = conditional_t&lt;ThreeWayComparable&lt;T&gt;, compare_3way_type_t&lt;T&gt;, C&gt;;

// and then we can just...
template &lt;typename T, typename U&gt;
struct pair {
    T first;
    U second;

    common_comparison_category_t&lt;
        fallback_to&lt;T, weak_ordering&gt;,
        fallback_to&lt;U, weak_ordering&gt;&gt;
    operator&lt;=&gt;(pair const&amp;) const = default;
};</code></pre>


<p><code class="language-cpp">pair&lt;T,U&gt;</code> is a simple type, we just want the default comparisons. Being able to default spaceship is precisely what we want. This proposal gets us there, with minimal acrobatics. Note that as a result of P1185R0, this would also give us a defaulted <code class="language-cpp">==</code>, and hence we get all six comparison functions in one go.</p>
<p>Building on this idea, we can create a wrapper type which defaults <code class="language-cpp">&lt;=&gt;</code> using these language rules for a single type, and wrap that into more complex function objects:</p>
<pre class="codehilite"><code class="language-cpp">// a type that defaults a 3-way comparison for T for the given category
template &lt;typename T, typename Cat&gt;
struct cmp_with_fallback {
    T const&amp; t;
    fallback_to&lt;T,Cat&gt; operator&lt;=&gt;(cmp_with_fallback const&amp;) const = default;
};

// Check if that wrapper type has a non-deleted &lt;=&gt;, whether because T
// has one or because T provides the necessary operators for one to be
// synthesized per this proposal
template &lt;typename T, typename Cat&gt;
concept FallbackThreeWayComparable =
    ThreeWayComparable&lt;cmp_with_fallback&lt;T, Cat&gt;&gt;;

// Function objects to do a three-way comparison with the specified fallback
template &lt;typename Cat&gt;
struct compare_3way_fallback_t {
    template &lt;FallbackThreeWayComparable&lt;Cat&gt; T&gt;
    constexpr auto operator()(T const&amp; lhs, T const&amp; rhs) {
        using C = cmp_with_fallback&lt;T, Cat&gt;;
        return C{lhs} &lt;=&gt; C{rhs};
    }
};

template &lt;typename Cat&gt;
inline constexpr compare_3way_fallback_t&lt;Cat&gt; compare_3way_fallback{};</code></pre>


<p>And now implementing <code class="language-cpp">&lt;=&gt;</code> for <code class="language-cpp">vector&lt;T&gt;</code> unconditionally is straightforward:</p>
<pre class="codehilite"><code class="language-cpp">template &lt;FallbackThreeWayComparable&lt;weak_ordering&gt; T&gt;
constexpr auto operator&lt;=&gt;(vector&lt;T&gt; const&amp; lhs, vector&lt;T&gt; const&amp; rhs) {
    // Use &lt;=&gt; if T has it, otherwise use a combination of either ==/&lt;
    // or just &lt; based on what T actually has. The proposed language
    // change does the right thing for us
    return lexicographical_compare_3way(
        lhs.begin(), lhs.end(),
        rhs.begin(), rhs.end(),
        compare_3way_fallback&lt;weak_ordering&gt;);
}</code></pre>


<p>As currently specified, <code class="language-cpp">std::weak_order()</code> and <code class="language-cpp">std::partial_order()</code> from [cmp.alg] basically follow the language rules proposed here. We can implement those with a slightly different approach to the above - no fallback necessary here because we need to enforce a particular category:</p>
<pre class="codehilite"><code class="language-cpp">template &lt;typename T, typename Cat&gt;
struct compare_as {
    T const&amp; t;
    Cat operator&lt;=&gt;(compare_as const&amp;) const = default;
};

// Check if the compare_as wrapper has non-deleted &lt;=&gt;, whether because T
// provides the desired comparison category or because we can synthesize one
template &lt;typename T, typename Cat&gt;
concept SyntheticThreeWayComparable = ThreeWayComparable&lt;compare_as&lt;T, Cat&gt;, Cat&gt;;

template &lt;SyntheticThreeWayComparable&lt;weak_ordering&gt; T&gt;
weak_ordering weak_order(T const&amp; a, T const&amp; b) {
    using C = compare_as&lt;T, weak_ordering&gt;;
    return C{a} &lt;=&gt; C{b};
}

template &lt;SyntheticThreeWayComparable&lt;partial_ordering&gt; T&gt;
partial_ordering partial_order(T const&amp; a, T const&amp; b) {
    using C = compare_as&lt;T, partial_ordering&gt;;
    return C{a} &lt;=&gt; C{b};
}</code></pre>


<p>None of the above is being proposed, it's just a demonstration that this language feature is sufficient to build up fairly complex tools in a short amount of code.</p>
<h3 id="what-about-compare_3way">3.5. What about <code class="language-cpp">compare_3way()</code>?<a class="self-link" href="#what-about-compare_3way"></a></h3>
<p>Notably absent from this paper has been a real discussion over the fate of <code class="language-cpp">std::compare_3way()</code>. R0 of this paper made this algorithm obsolete, but that's technically no longer true. It does, however, fall out from the tools we will need to build up in code to solve other problems. In fact, we've already written it:</p>
<pre class="codehilite"><code class="language-cpp">constexpr inline auto compare_3way = compare_3way_fallback&lt;strong_ordering&gt;;</code></pre>


<p>For further discussion, see <a href="https://wg21.link/p1188r0" title="Library utilities for &lt;=&gt;">P1188R0</a>. This paper focuses just on the language change for <code class="language-cpp">operator&lt;=&gt;</code>.</p>
<h3 id="what-about-xxx_equality">3.6. What about <code class="language-cpp">XXX_equality</code>?<a class="self-link" href="#what-about-xxx_equality"></a></h3>
<p>This paper proposes synthesizing <code class="language-cpp">strong_equality</code> and <code class="language-cpp">weak_equality</code> orderings, simply for consistency, even if such return types from <code class="language-cpp">operator&lt;=&gt;</code> are somewhat questionable. As long as we have language types for which <code class="language-cpp">&lt;=&gt;</code> yields a comparison category of type <code class="language-cpp">XXX_equality</code>, all the rules we build on top of <code class="language-cpp">&lt;=&gt;</code> should respect that and be consistent. </p>
<h2 id="wording">4. Wording<a class="self-link" href="#wording"></a></h2>
<p>Remove a sentence from 10.10.2 [class.spaceship], paragraph 1:</p>
<blockquote>
<p>Let <code>x<sub>i</sub></code> be an lvalue denoting the ith 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 ([over.best.ics]), class member access expressions ([expr.ref]), and array subscript expressions ([expr.sub]) applied to x. The type of the expression <code>x<sub>i</sub> &lt;=&gt; x<sub>i</sub></code> is denoted by <del><code>R<sub>i</sub></code>.</del> <ins><code>S<sub>i</sub></code>. If the expression is ill-formed, <code>S<sub>i</sub></code> is <code class="language-cpp">void</code>.</ins> It is unspecified whether virtual base class subobjects are compared more than once.</p>
</blockquote>
<p><a name="3way-def"></a>Insert a new paragraph after 10.10.2 [class.spaceship], paragraph 1:</p>
<blockquote>
<p><ins>Define <code><i>3WAY</i>&lt;R&gt;(a, b)</code> as follows:</ins></p>
<ul>
<li><ins>If <code class="language-cpp">a &lt;=&gt; b</code> is well-formed and convertible to <code class="language-cpp">R</code>, <code class="language-cpp">a &lt;=&gt; b</code>;</ins></li>
<li><ins>Otherwise, if <code class="language-cpp">a &lt;=&gt; b</code> is well-formed, <code><i>3WAY</i>&lt;R&gt;(a, b)</code> is ill-formed;</ins></li>
<li><ins>Otherwise, if <code class="language-cpp">R</code> is <code class="language-cpp">strong_ordering</code>, then <code class="language-cpp">(a == b) ? strong_ordering::equal : ((a &lt; b) ? strong_ordering::less : strong_ordering::greater)</code>;</ins></li>
<li><ins>Otherwise, if <code class="language-cpp">R</code> is <code class="language-cpp">weak_ordering</code>, then:</ins><ul>
<li><ins>If <code class="language-cpp">a == b</code> is well-formed and convertible to <code class="language-cpp">bool</code>, then <code class="language-cpp">(a == b) ? weak_ordering::equivalent : ((a &lt; b) ? weak_ordering::less : weak_ordering::greater)</code>;</ins></li>
<li><ins>Otherwise, <code class="language-cpp">(a &lt; b) ? weak_ordering::less : ((b &lt; a) ? weak_ordering::greater : weak_ordering::equivalent)</code>;</ins></li>
</ul>
</li>
<li><ins>Otherwise, if <code class="language-cpp">R</code> is <code class="language-cpp">partial_ordering</code>, then <code class="language-cpp">(a == b) ? partial_ordering::equivalent : ((a &lt; b) ? partial_ordering::less : ((b &lt; a) ? partial_ordering::greater : partial_ordering::unordered))</code>;</ins></li>
<li><ins>Otherwise, if <code class="language-cpp">R</code> is <code class="language-cpp">strong_equality</code>, then <code class="language-cpp">(a == b) ? strong_equality::equal : strong_equality::nonequal</code>;</ins></li>
<li><ins>Otherwise, if <code class="language-cpp">R</code> is <code class="language-cpp">weak_equality</code>, then <code class="language-cpp">(a == b) ? weak_equality::equivalent : weak_equality::nonequivalent</code>;</ins></li>
<li><ins>Otherwise, <code><i>3WAY</i>&lt;R&gt;(a, b)</code> is ill-formed.</ins></li>
</ul>
</blockquote>
<p>Change 10.10.2 [class.spaceship], paragraph 2 (note that we do <em>not</em> want to make the noted case ill-formed, we just want to delete the operator):</p>
<blockquote>
<p>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 <ins><code>S<sub>0</sub></code>, <code>S<sub>1</sub></code>, …, <code>S<sub>n-1</sub></code>.</ins> <del><code>R<sub>0</sub></code>, <code>R<sub>1</sub></code>, …, <code>R<sub>n-1</sub></code>. [ Note: Otherwise, the program will be ill-formed if the expression <code>x<sub>i</sub> &lt;=&gt; x<sub>i</sub></code> is not implicitly convertible to the declared return type for any <code>i</code>. — end note ]</del> If the return type is deduced as <code class="language-cpp">void</code>, the operator function is defined as deleted.</p>
<p><ins>If the declared return type of a defaulted three-way comparison operator function is <code class="language-cpp">R</code> and any <code><i>3WAY</i>&lt;R&gt;(x<sub>i</sub>,x<sub>i</sub>)</code> is ill-formed, the operator function is defined as deleted.</ins></p>
</blockquote>
<p>Change 10.10.2 [class.spaceship], paragraph 3, to use <code class="language-cpp">3WAY</code> instead of <code class="language-cpp">&lt;=&gt;</code></p>
<blockquote>
<p>The return value <code class="language-cpp">V</code> of type <code class="language-cpp">R</code> of the defaulted three-way comparison operator function with parameters <code class="language-cpp">x</code> and <code class="language-cpp">y</code> of the same type 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 <del>x<sub>i</sub> &lt;=&gt; y<sub>i</sub></del> <ins><code><i>3WAY</i>&lt;R&gt;(x<sub>i</sub>, y<sub>i</sub>)</code></ins> yields a result value <code>v<sub>i</sub></code> where <code>v<sub>i</sub> != 0</code>, contextually converted to <code class="language-cpp">bool</code>, yields <code class="language-cpp">true</code>; <code class="language-cpp">V</code> is <code>v<sub>i</sub></code> converted to <code class="language-cpp">R</code>. If no such index exists, <code class="language-cpp">V</code> is <code class="language-cpp">std::strong_ordering::equal</code> converted to <code class="language-cpp">R</code>. </p>
</blockquote>
<h2 id="acknowledgments">5. Acknowledgments<a class="self-link" href="#acknowledgments"></a></h2>
<p>Thanks to Gašper Ažman, Agustín Bergé, Richard Smith, Jeff Snyder, Tim Song, Herb Sutter, and Tony van Eerd for the many discussions around these issues. Thanks to the Core Working Group for being vigilant and ensuring a better proposal.</p>
<h2 id="references">6. 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/p1185r0.html">[P1185R0]</a><span style="margin-left: 5px;">"&lt;=&gt; != ==" by Barry Revzin, 2018-10-07</span></li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1186r0.html">[P1186R0]</a><span style="margin-left: 5px;">"When do you actually use &lt;=&gt;?" by Barry Revzin, 2018-10-07</span></li><li><a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1188r0.html">[P1188R0]</a><span style="margin-left: 5px;">"Library utilities for &lt;=&gt;" by Barry Revzin, 2019-01-20</span></li><li><a href="https://brevzin.github.io/c++/2018/12/21/spaceship-for-vector/">[revzin.sometimes]</a><span style="margin-left: 5px;">"Conditionally implementing spaceship" by Barry Revzin, 2018-12-21</span></li></ul>
</html>