Repository: tvaneerd/cpp20_in_TTs
Branch: main
Commit: de65840aaf69
Files: 12
Total size: 26.3 KB
Directory structure:
gitextract_ymp9_jlx/
├── README.md
├── abbreviated_function_template.md
├── attributes.md
├── boolean_explicit.md
├── concepts.md
├── consteval_constinit.md
├── feature_tracking.md
├── for-with-init.md
├── format.md
├── remove-cvref.md
├── spaceship-operator.md
└── templatized_lambdas.md
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# cpp20_in_TTs
C++20 features described in Before/After tables ("Tony Tables")
This is an attempt to succinctly describe the main (not all) features of C++20, and to update these docs as features are moved through the committee.
See the list of files in the repo — typically one file per feature (hopefully obvious via the file name).
Once C++20 is finalizing, we can put it all together into a single doc.
Many of the examples come directly from the original committee proposals (particularly since we started asking for TTs and/or clear examples in the proposals!), so thanks to the proposal authors.
And thanks to [eracpp](https://github.com/eracpp) for a number of contributions!
See the [C++17 "Tony Tables"](https://github.com/tvaneerd/cpp17_in_TTs).
Created with help the support from my employer, [Christie Digital Systems, Inc.](http://christiedigital.com)
================================================
FILE: abbreviated_function_template.md
================================================
Abbreviated Function Template
---
Generic lambdas were introduced in C\++14, where auto was used as a function parameter.
<table>
<tr>
<th>
C++14
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
// lambda definition
auto addTwo = [](auto first, auto second)
{
return first + second;
};
</pre>
</td>
</tr>
</table>
In C\++20, similarly to how auto was used in lambdas, auto can now be used as a function parameter to get a function template. \
C++20 has added abbreviated function templates, used to shorten the template version of the function.
<table>
<tr>
<th>
C++
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
template< typename T >
T addTwo(T first, T second)
{
return first + second;
}
</pre>
</td>
<td valign="top">
<pre lang="cpp">
// this is a template, without the word 'template'!
auto addTwo(auto first, auto second)
{
return first + second;
}
</pre>
</td>
</tr>
</table>
(To be clear - it is the `auto` used as a param - even just one of the params - that makes it a template.)
Looking at it from the point of view of Concepts, `auto` acts like the most unconstrained Concept.
================================================
FILE: attributes.md
================================================
some new [[attributes]]
---
**[[no_unique_address]]**
If a data member need not have a distinct address, then the compiler may optimise it to occupy no space.
For an object to occupy no space, these requirements must apply:
1. The object is empty.
i. Objects of type std::allocator do not actually store anything (stateless allocators)
2. Empty struct/class
2. The second object is a non-static data member from the same class that must be:
i. Of a different type or have a different address in memory
<table>
<tr>
<th>
C++17
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
struct Empty {};
template < typename T, typename S >
struct Test {
T t;
S s;
};
int main() {
static_assert(sizeof(Test< Empty,char >) >= sizeof(char) + 1); // 2
static_assert(sizeof(Test< Empty,int >) >= sizeof(int) + 1); // 8
}
</pre>
</td>
<td valign="top">
<pre lang="cpp">
struct Empty {};
template < typename T, typename S >
struct Test {
[[no_unique_address]] T t;
[[no_unique_address]] S s;
};
int main() {
// Different type of objects
static_assert(sizeof(Test< Empty,char >) == sizeof(char)); // 1
static_assert(sizeof(Test< Empty,int >) == sizeof(int)); // 4
// Same objects
static_assert(sizeof(Test< Empty,Empty >) == 2);
}
</pre>
</td>
</tr>
</table>
**[[likely]]** and **[[unlikely]]**
The use of these attributes aid the compiler in optimizing for the case in which the paths of execution
are more or less likely than any alternative paths of execution that do not include the usage of these attributes.
<table>
<tr>
<th>
C++17
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
{
const auto start = std::chrono::high_resolution_clock::now();
for (int i = 1; i <= 9000; i++) {
if (i % 5 == 0) {
std::cout << i << " is div by 5" << std::endl;
}
else {
continue;
}
}
const std::chrono::duration<double> diff = std::chrono::high_resolution_clock::now() - start;
std::cout << "Time: " << diff.count() << " sec " << std::endl;
}
// When using a number < 9000, the attributes did not seem to have much of an impact
</pre>
</td>
<td valign="top">
<pre lang="cpp">
{
const auto start = std::chrono::high_resolution_clock::now();
for (int i = 1; i <= 9000; i++) {
if (i % 5 == 0) [[unlikely]] {
std::cout << i << " is div by 5" << std::endl;
}
else [[likely]] {
continue;
}
}
const std::chrono::duration<double> diff = std::chrono::high_resolution_clock::now() - start;
std::cout << "Time: " << diff.count() << " sec " << std::endl;
}
</pre>
</td>
</tr>
<tr>
<th>
Output
</th>
<th>
Output
</th>
</tr>
<tr>
<td valign="top">
Time: 0.00655365 sec
</td>
<td valign="top">
Time: 0.00608944 sec
</td>
</tr>
</table>
Both attributes do not need to be used, only using one of **[[likely]]** or **[[unlikely]]** would suffice.
They can also be used in switch statements.
<table>
<tr>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
bool enoughCoffee(int coffeeLeft) {
switch(coffeeLeft){
case 0: return false;
[[likely]] case 1: return false;
}
return true;
}
</pre>
</td>
</tr>
</table>
================================================
FILE: boolean_explicit.md
================================================
_explicit(boolean-expression)_
<table>
<tr>
<th>
C++17
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
struct MyInt
{
int val = 0;
// explicit if the int type coming in
// is bigger than what we can hold.
// eg explicit if you pass a long,
// implicit if you pass a short
// TODO: handle unsigned...
template <typename Init,
std::enable_if_t<(sizeof(Init) > sizeof(int)),
int> = 0>
explicit MyInt(Init init)
: val(static_cast<int>(init))
{
}
template <typename Init,
std::enable_if_t<(sizeof(Init) <= sizeof(int)),
int> = 0>
MyInt(Init init)
: val(static_cast<int>(init))
{
}
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">
struct MyInt
{
int val = 0;
// explicit if the int type coming in
// is bigger than what we can hold.
// eg explicit if you pass a long,
// implicit if you pass a short
// (TODO: check for unsigned...)
template <typename Init>
explicit(sizeof(Init) > sizeof(int))
MyInt(Init init)
: val(static_cast<int>(init))
{
}
};
</pre>
</td>
</tr>
</table>
That can be useful on its own. But an interesting corollary is "explicit(false)".
What's the point of that over the "equivalent" of just not saying explicit at all?
It is being explicit about an implicit constructor. For example:
<table>
<tr>
<th>
Questionable C++
</th>
<th>
Better C++
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
struct Foo
{
Foo(Bar b);
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">
struct Foo
{
/* yes, implicit*/ Foo(Bar b);
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">
struct Foo
{
explicit(false) Foo(Bar b);
};
</pre>
</td>
</tr>
</table>
In code review of the first example, I would probably ask if the constructor should be explicit - the general rule is that most constructors should be explicit (it is another default that C++ got wrong).
(because implicit conversions often lead to mistaken conversions, which leads to bugs)
So some people leave comments, to make it clear. But comments that just explain syntax (and not _why_) are better done as more clear code.
Using `explicit(false)` shows that the implicitness wasn't an accidental omission, but a real design decision. (You might still want to leave a comment as to _why_ that is the right design decision.)
P.S. Please don't `#define implicit explicit(false)`. _Just Don't_. Do Not. Not Do. Don't even Try.
See also [wg21.link/p0892](https://wg21.link/p0892)
================================================
FILE: concepts.md
================================================
Concepts. Whew, what a concept.
<table>
<tr>
<th>
C++17
</th>
<th align="left">
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
#include <vector>
#include <list>
#include <algorithm>
template <typename Container>
void sort(Container & c)
{
std::sort(c.begin(), c.end());
}
void test()
{
std::list<int> ls = {3, 2, 1, 5, 4};
sortContainer(ls);
}
</pre>
</td>
<td valign="top">
<pre lang="cpp">
#include <vector>
#include <list>
#include <algorithm>
#include <ranges>
void sort(std::ranges::random_access_range auto & c)
{
std::sort(c.begin(), c.end());
}
void test()
{
std::list<int> ls = { 1, 2, 3, 4, 5, 6 };
sortContainer(ls);
}
</pre>
</td>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
**4375 lines of compiler error!!!**
S E R I O U S L Y
</pre>
</td>
<td valign="top">
<pre lang="cpp">
source: In function 'void test()':
source:27:21: error: no matching function for call to 'sortContainer(std::__cxx11::list<int>&)'
27 | sortContainer(ls);
| ^
source:17:6: note: candidate: 'template<class Container, class auto:16> requires random_access_range<auto:16> void sortContainer(auto:16&)'
17 | void sortContainer(std::ranges::random_access_range auto & c)
| ^~~~~~~~~~~~~
source:17:6: note: template argument deduction/substitution failed:
source:27:21: note: couldn't deduce template parameter 'Container'
27 | sortContainer(ls);
| ^
ASM generation compiler returned: 1
source: In function 'void test()':
source:27:21: error: no matching function for call to 'sortContainer(std::__cxx11::list<int>&)'
27 | sortContainer(ls);
| ^
source:17:6: note: candidate: 'template<class Container, class auto:16> requires random_access_range<auto:16> void sortContainer(auto:16&)'
17 | void sortContainer(std::ranges::random_access_range auto & c)
| ^~~~~~~~~~~~~
source:17:6: note: template argument deduction/substitution failed:
source:27:21: note: couldn't deduce template parameter 'Container'
27 | sortContainer(ls);
| ^
Execution build compiler returned: 1
</pre>
</td>
</tr>
</table>
================================================
FILE: consteval_constinit.md
================================================
New Keywords - consteval and constinit
---
### consteval
A function specified with the keyword **consteval** is an immediate function that is executed at compile-time.
Each call to this function must produce a compile-time constant expression.
This immediate function must satisfy all requirements applicable to **constexpr** functions.
<table>
<tr>
<th>
C++14 - constexpr function
</th>
<th>
C++20 - consteval function
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
// executes at compile-time or run-time
constexpr int squareNumX(int n)
{
return n * n;
}
</pre>
</td>
<td valign="top">
<pre lang="cpp">
// executes at compile-time
consteval int squareNumV(int n)
{
return n * n;
}
</pre>
</td>
</tr>
</table>
Immediate functions (specified with consteval) cannot be applied to:
- destructors
- functions which allocate or deallocate
<table>
<tr>
<th>
C++14
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
{
int x = 100;
const int y = 100;
int a = squareNumX(x);
constexpr int b = squareNumX(x); // ERROR
int c = squareNumX(y);
constexpr int d = squareNumX(y);
}
// Error when x is not a constant expression but b is
</pre>
</td>
<td valign="top">
<pre lang="cpp">
{
int x = 100;
const int y = 100;
int a = squareNumV(x); // ERROR
constexpr int b = squareNumV(x); // ERROR
int c = squareNumV(y);
constexpr int d = squareNumV(y);
}
// Error when the function argument (x) is not a constant expression
</pre>
</td>
</tr>
</table>
### constinit
Applied to variables with static storage duration or thread storage duration.
**constinit** variables are initialized at compile-time.
**constinit** does not imply constness such as const or constexpr(2).
<table>
<tr>
<th>
C++14 - constexpr
</th>
<th>
C++20 - constinit
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
// static storage duration
constexpr int a = 100;
int main()
{
++a; // ERROR
constexpr auto b = 100;
}
// Error since constexpr or const cannot be modifed
// constexpr or const can be created locally
</pre>
</td>
<td valign="top">
<pre lang="cpp">
// static storage duration
constinit int a = 100;
int main()
{
++a;
constinit auto b = 100; // ERROR
// b has thread storage duration
constinit thread_local auto b = 100;
}
// Error since constinit cannot be created locally
// constinit can be modified
</pre>
</td>
</tr>
</table>
================================================
FILE: feature_tracking.md
================================================
Features that need to be covered (mostly in order from major to minor)
Concepts
Coroutines
more constexpr, constinit, constexpr containers, NTTP
Modules
Ranges
Chrono date-time
<=>
format
span
jthread
latches/barriers, etc
source_location
================================================
FILE: for-with-init.md
================================================
Range-based for, with init
---
C++17 gave us if statements with initializers, like:
<table>
<tr>
<th>
C++17
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
{
// note the semicolon
if (QVariant var = getAnswer(); var.isValid())
use(var);
more_code();
}
</pre>
</td>
</tr>
</table>
C++20 adds a similar init, for range-based `for` statements
<table>
<tr>
<th>
C++
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
{
T thing = f();
for (auto& x : thing.items()) {
// Note: “for (auto& x : f().items())” is WRONG
mutate(&x);
log(x);
}
}
</pre>
</td>
<td valign="top">
<pre lang="cpp">
for (T thing = f(); auto& x : thing.items()) {
mutate(&x);
log(x);
}
</pre>
</td>
</tr>
</table>
Also helps with "combined" index-y and range-y loops:
<table>
<tr>
<th>
C++
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
{
std::size_t i = 0;
for (const auto& x : foo()) {
bar(x, i);
++i;
}
}
</pre>
</td>
<td valign="top">
<pre lang="cpp">
for (std::size_t i = 0; const auto& x : foo()) {
bar(x, i);
++i;
}
</pre>
</td>
</tr>
</table>
_These examples, already in table form, were taken directly from the paper [wg21.link/p0614](http://wg21.link/p0614). Thanks Thomas!_
================================================
FILE: format.md
================================================
`std::format` is a new way to format text which
- separates formatting from outputting
- is more type-safe
- allows reordering
<table>
<tr>
<th>
C++17
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
std::printf("%d baskets of %s", n, desc);
//OR
std::cout << n << " baskets of " << desc;
</pre>
</td>
<td valign="top">
<pre lang="cpp">
std::cout << std::format("{} baskets of {}", n, desc);
</pre>
</td>
</tr>
</table>
#### Ordering
<table>
<tr>
<th>
whoops!
</th>
<th>
Apples! 5 baskets left
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
std::printf("%s! %d baskets left", n, desc);
</pre>
</td>
<td valign="top">
<pre lang="cpp">
std::cout <<
std::format("{1}! {0} baskets left", n, desc);
</pre>
</td>
</tr>
</table>
#### Formatting
Detailed formatting is similar to printf, but actually based on [Python!](https://docs.python.org/3/library/string.html#formatspec)
The general form is
_fill-and-align sign `#` `0` width precision `L` type_
(with each part optional).
<table>
<tr>
<th>
</th>
<th>
format
</th>
<th>
result
</th>
</tr>
<tr>
<td/>
<td valign="top">
<pre lang="cpp">
std::string s;
// fill-and-align
s = std::format("{:6}", 123); //default
s = std::format("{:<6}", 123); //left
s = std::format("{:^6}", 123); //center
s = std::format("{:>6}", 123); //right
s = std::format("{:6}", "abc"); //default
s = std::format("{:<6}", "abc"); //left
s = std::format("{:^6}", "abc"); //center
s = std::format("{:>6}", "abc"); //right
s = std::format("{:$^6}", "abc"); //fill with
// sign
s = std::format("{}", 123); //default
s = std::format("{}", -123); //default
s = std::format("{:-}", 123); //same as def
s = std::format("{:-}", -123); //same as def
s = std::format("{:+}", 123); //always sign
s = std::format("{:+}", -123); //always sign
s = std::format("{: }", 123); //space if pos
s = std::format("{: }", -123); //space if pos
</pre>
</td>
<td valign="top">
<pre lang="cpp">
// results...
// fill-and-align
assert(s == " 123"); // numbers >
assert(s == "123 ");
assert(s == " 123 "); // slightly <
assert(s == " 123");
assert(s == "abc "); // strings <
assert(s == "abc ");
assert(s == " abc "); // slightly <
assert(s == " abc");
assert(s == "$abc$$"); // fill with $
// sign
/* {} */ assert(s == "123");
/* {} */ assert(s == "-123");
/*{:-}*/ assert(s == "123");
/*{:-}*/ assert(s == "-123");
/*{:+}*/ assert(s == "+123");
/*{:+}*/ assert(s == "-123");
/*{: }*/ assert(s == " 123");
/*{: }*/ assert(s == "-123");
</pre>
</td>
</tr>
</table>
#### Chrono
<table>
<tr>
<th>
C++17
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
???
</pre>
</td>
<td valign="top">
<pre lang="cpp">
std::cout << std::format("The time is {}", std::system_clock::now());
</pre>
</td>
</tr>
</table>
================================================
FILE: remove-cvref.md
================================================
std::remove_cvref
===================
Summary
-------
<table>
<tr>
<th>Before C++20</th>
<th>After C++20</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
typename std::remove_cv<typename std::remove_reference<T>::type>::type
std::remove_cv_t<std::remove_reference_t<T>>
</pre>
</td>
<td valign="top">
<pre lang="cpp">
typename std::remove_cvref<T>::type
std::remove_cvref_t<T>
</pre>
</td>
</tr>
</table>
Detail
------
`std::decay<T>` is often (mis)used to obtain the type name for a type that
is potentially a reference, cv-qualified, or both:
<pre lang="cpp">
template <typename T>
auto f(T&& t) {
// Removes reference and cv-qualifiers...
using type_name = std::decay_t<T>;
// To avoid this problem...
static_assert(!std::is_same_v<int&, int>);
static_assert(!std::is_same_v<int const, int>);
// When evaluating the following conditional expression.
if constexpr (std::is_same_v<type_name, int>) {
use_int(t);
}
// Similarly, a reference type is not an array...
else if constexpr (std::is_array_v<type_name>) {
use_array(t);
}
// Nor is it a function.
else if constexpr (std::is_function_v<type_name>) {
use_function(t);
}
else {
use_other(t);
}
}
int main() {
auto const i = 42;
auto const s = std::string();
f(i); // T == 'int const&', type_name == 'int'; invokes 'use_int'.
f(s); // T == 'std::string const&', type_name == 'std::string'; invokes 'use_other'.
}
</pre>
However, `std::decay<T>` introduces decay semantics for array and
function types:
<pre lang="cpp">
int arr[3];
// T == 'int (&)[3]', but type_name == 'int*' due to array-to-pointer decay.
// 'std::is_array_v<int*>' is false; invokes 'use_other'.
f(arr);
void g();
// T == 'void (&)()', but type_name == 'void (*)()' due to function-to-pointer decay.
// 'std::is_function<void (*)()>' is false; invokes 'use_other'.
f(g);
</pre>
In C++20, the standard library provides the `std::remove_cvref<T>`
type trait to fulfill this purpose without introducing unwanted
decay semantics:
<pre lang="cpp">
template <typename T>
auto f(T&& t) {
using type_name = std::remove_cvref_t<T>;
...
}
int main() {
auto const i = 42;
int arr[3];
void g();
f(0); // Same as before.
f(i); // Same as before.
f(arr); // T == 'int (&)[3]', type_name == 'int [3]'; invokes 'use_array'.
f(g); // T == 'void (&)()', type_name == 'void()'; invokes 'use_function'.
}
</pre>
================================================
FILE: spaceship-operator.md
================================================
Better Equality
===============
<table>
<tr>
<th>Before C++20</th>
<th>After C++20</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
class Chair
{
int numLegs = 4;
Color color = RGB(64, 255, 128);
Style style;
public:
friend bool operator==(Chair const & a, Chair const & b)
{
return a.numLegs == b.numLegs
&& a.color == b.color
&& a.style == b.style;
}
friend bool operator!=(Chair const & a, Chair const & b)
{
return !(a == b);
}
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">
class Chair
{
int numLegs = 4;
Color color = RGB(128, 128, 128);
Style style;
public:
auto operator==(Chair const &) const = default;
};
</pre>
</td>
</tr>
</table>
Spaceship Operator
==================
<table>
<tr>
<th>Before C++20</th>
<th>After C++20</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
class MyInt {
int value = 0;
public:
friend bool operator==(MyInt a, MyInt b) {
return a.value == b.value;
}
friend bool operator!=(MyInt a, MyInt b) {
return !(a == b);
}
friend bool operator<(MyInt a, MyInt b) {
return a.value < b.value;
}
friend bool operator<=(MyInt a, MyInt b) {
return !(b < a);
}
friend bool operator>(MyInt a, MyInt b) {
return b < a;
}
friend bool operator>=(MyInt a, MyInt b) {
return !(a < b);
}
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">class MyInt {
int value = 0;
public:
auto operator<=>(MyInt) const = default;
};
</pre>
</td>
</tr>
</table>
Details
=======
Usage
-----
C++20 introduces the *three-way comparison operator*, also known as the
"spaceship operator":
class Person {
std::string lastname;
std::string firstname;
public:
auto operator<=>(Person const&) const = default;
};
The spaceship operator behaves similarly to `strcmp` and `memcmp`:
<table>
<tr>
<th><code>strcmp</code>/<code>memcmp</code></th>
<th><code>operator<=></code></th>
<th>Meaning</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
if (strcmp(a, b) < 0);
</pre>
</td>
<td valign="top">
<pre lang="cpp">
if (auto cmp = a <=> b; cmp < 0);
</pre>
</td>
<td valign="top">
<p>If <code>a</code> is less than <code>b</code>.</p>
</td>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
if (strcmp(a, b) > 0);
</pre>
</td>
<td valign="top">
<pre lang="cpp">
if (auto cmp = a <=> b; cmp > 0);
</pre>
</td>
<td valign="top">
<p>If <code>a</code> is greater than <code>b</code>.</p>
</td>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
if (strcmp(a, b) == 0);
</pre>
</td>
<td valign="top">
<pre lang="cpp">
if (auto cmp = a <=> b; cmp == 0);
</pre>
</td>
<td valign="top">
<p>If <code>a</code> is equal to <code>b</code>.</p>
</td>
</tr>
</table>
Thanks to [Symmetry for Spaceship \[P0905\]](http://wg21.link/p0905),
if `a <=> b` is well-formed, then `b <=> a` is also well-formed. This
means only one definition of `operator<=>` is required to provide
heterogeneous type comparison (it does not need to be a (`friend`-ed)
non-member function).
Existing equality and comparison operator overloads take precedence over
the spaceship operator. This means **this is not a breaking change**,
and users can still specialize particular operators as desired.
> **Note**
>
> The spaceship operator resembles a TIE **bomber** `<=>`, not a TIE
> **fighter** `|=|`.
Comparison Categories
---------------------
C++20 also introduces *comparison categories* to specify weak/partial/strong ordering.
ie ints have strong_ordering, but floats have partial_ordering (due to NaNs). Weak_ordering is for orderings where `!(a < b) && !(b < b)` does NOT imply `a == b`. ie an ordering that builds _equivalence classes_. For example, coloured bingo balls that are sorted by number (ignoring color), but where equality _does_ check color.
Recommendations:
- every copyable class should have equality. _Copy_ means _an equal copy_.
- most classes don't actually need `<=>`, as they don't have _natural_ order. ie `myChair < yourChair` doesn't make much sense. Ordering Chairs in various ways does make sense, but there is no obvious natural order. If you want to order Chairs to place into a map or set, consider instead unordered_map/set. If you really want map/set, then pass in a specific ordering, don't rely on `std::less`.
- If your class has a natural ordering, I really hope it is strong_ordering. Users will thank you. In particular, if you have a weak_ordering, consider instead a named ordering function, not overriding the built in operators.
> **Note**
>
> Original paper: [Consistent Comparison \[P0515\]](http://wg21.link/p0515).
> Subsequent papers:
> [`<=> != ==`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1185r0.html)
> [When do you actually use `<=>`?](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1186r0.html)
> [I did not order this! Why is it on my bill?](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1190r0.html)
> [weak_equality considered harmful](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1307r0.pdf)
================================================
FILE: templatized_lambdas.md
================================================
#### TL;DR
C++14 gave us _generic lambdas_ where `auto` was used instead of a fixed type, turning the lambda into something of a template. ie
`auto inc = [](auto thing) { thing++; } // works on anything that can be incremented!`
But you couldn't do a bunch of other templaty things, like say `typename T` and then use `T`, etc.
Now you can.
#### Bonus?
Is `[]<>(){}` now valid syntax? Not quite. The `<>` can't be empty, at least not yet. A few of us want to make that valid... somehow :-)
#### Examples
Limit a lambda to only work with vector.
In addition to making _intent_ more clear,
a compiler error from the left (if you don't pass a vector) would be much worse than from the right.
<table>
<tr>
<th>
C++
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
template<typename T>
struct is_vector : std::false_type{};
template<typename T>
struct is_vector<std::vector<T>> : std::true_type{};
auto f = [](auto vector) {
static_assert(is_vector<decltype(vector)>::value, "");
//...
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">
auto f = []<typename T>(std::vector<T> vector) {
//...
};
</pre>
</td>
</tr>
</table>
Using _types_ inside a generic lambda was hard, verbose, annoying:
<table>
<tr>
<th>
C++
</th>
<th>
C++20
</th>
</tr>
<tr>
<td valign="top">
<pre lang="cpp">
// luckily vector gives you a way to get its inner type,
// most templates don't!
auto f = [](auto vector) {
using T = typename decltype(vector)::value_type;
//...
};
</pre>
</td>
<td valign="top">
<pre lang="cpp">
auto f = []<typename T>(std::vector<T> vector) {
//...
};
</pre>
</td>
</tr>
</table>
#### See also
Examples are mostly from the original proposal (https://wg21.link/p0428) however Louis didn't use Tony Tables in the original. Booo, hiss.
The proposal also includes more examples of how ugly it could get in C++14.
gitextract_ymp9_jlx/ ├── README.md ├── abbreviated_function_template.md ├── attributes.md ├── boolean_explicit.md ├── concepts.md ├── consteval_constinit.md ├── feature_tracking.md ├── for-with-init.md ├── format.md ├── remove-cvref.md ├── spaceship-operator.md └── templatized_lambdas.md
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (29K chars).
[
{
"path": "README.md",
"chars": 872,
"preview": "# cpp20_in_TTs\nC++20 features described in Before/After tables (\"Tony Tables\")\n\nThis is an attempt to succinctly describ"
},
{
"path": "abbreviated_function_template.md",
"chars": 1146,
"preview": "Abbreviated Function Template\n---\n\nGeneric lambdas were introduced in C\\++14, where auto was used as a function paramete"
},
{
"path": "attributes.md",
"chars": 3318,
"preview": "some new [[attributes]]\n---\n\n\n**[[no_unique_address]]**\n\nIf a data member need not have a distinct address, then the com"
},
{
"path": "boolean_explicit.md",
"chars": 2659,
"preview": "\n_explicit(boolean-expression)_\n\n\n\n<table>\n<tr>\n<th>\nC++17\n</th>\n<th>\nC++20\n</th>\n</tr>\n<tr>\n<td valign=\"top\">\n\n<pre la"
},
{
"path": "concepts.md",
"chars": 2270,
"preview": "Concepts. Whew, what a concept.\n\n\n<table>\n<tr>\n<th>\nC++17\n</th>\n<th align=\"left\">\nC++20\n</th>\n</tr>\n\n<tr>\n<td valign=\"t"
},
{
"path": "consteval_constinit.md",
"chars": 2508,
"preview": "New Keywords - consteval and constinit\n---\n\n### consteval \nA function specified with the keyword **consteval** is an imm"
},
{
"path": "feature_tracking.md",
"chars": 263,
"preview": "Features that need to be covered (mostly in order from major to minor)\n\nConcepts \nCoroutines \nmore constexpr, constini"
},
{
"path": "for-with-init.md",
"chars": 1306,
"preview": "Range-based for, with init\n---\n\nC++17 gave us if statements with initializers, like:\n\n<table>\n<tr>\n<th>\nC++17\n</th>\n</tr"
},
{
"path": "format.md",
"chars": 2860,
"preview": "`std::format` is a new way to format text which\n\n- separates formatting from outputting\n- is more type-safe\n- allows reo"
},
{
"path": "remove-cvref.md",
"chars": 2590,
"preview": "std::remove_cvref\n===================\n\nSummary\n-------\n\n<table>\n<tr>\n<th>Before C++20</th>\n<th>After C++20</th>\n</tr>\n<t"
},
{
"path": "spaceship-operator.md",
"chars": 5211,
"preview": "Better Equality\n===============\n\n<table>\n<tr>\n<th>Before C++20</th>\n<th>After C++20</th>\n</tr>\n<tr>\n<td valign=\"top\">\n\n<"
},
{
"path": "templatized_lambdas.md",
"chars": 1957,
"preview": "#### TL;DR\n\nC++14 gave us _generic lambdas_ where `auto` was used instead of a fixed type, turning the lambda into somet"
}
]
About this extraction
This page contains the full source code of the tvaneerd/cpp20_in_TTs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (26.3 KB), approximately 8.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.