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.
| C++14 |
|---|
// lambda definition
auto addTwo = [](auto first, auto second)
{
return first + second;
};
|
| C++ | C++20 |
|---|---|
template< typename T >
T addTwo(T first, T second)
{
return first + second;
}
|
// this is a template, without the word 'template'!
auto addTwo(auto first, auto second)
{
return first + second;
}
|
| C++17 | C++20 |
|---|---|
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
}
|
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);
}
|
| C++17 | C++20 |
|---|---|
{
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
|
{
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
|
| Output | Output |
| Time: 0.00655365 sec | Time: 0.00608944 sec |
| C++20 |
|---|
bool enoughCoffee(int coffeeLeft) {
switch(coffeeLeft){
case 0: return false;
[[likely]] case 1: return false;
}
return true;
}
|
| C++17 | C++20 |
|---|---|
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))
{
}
};
|
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))
{
}
};
|
| Questionable C++ | Better C++ | C++20 |
|---|---|---|
struct Foo
{
Foo(Bar b);
};
|
struct Foo
{
/* yes, implicit*/ Foo(Bar b);
};
|
struct Foo
{
explicit(false) Foo(Bar b);
};
|
| C++17 | C++20 |
|---|---|
#include <vector>
#include <list>
#include <algorithm>
template <typename Container>
void sort(Container & c)
{
std::sort(c.begin(), c.end());
}
void test()
{
std::list
|
#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
|
**4375 lines of compiler error!!!**
S E R I O U S L Y
|
source: In function 'void test()':
source:27:21: error: no matching function for call to 'sortContainer(std::__cxx11::list
|
| C++14 - constexpr function | C++20 - consteval function |
|---|---|
// executes at compile-time or run-time
constexpr int squareNumX(int n)
{
return n * n;
}
|
// executes at compile-time
consteval int squareNumV(int n)
{
return n * n;
}
|
| C++14 | C++20 |
|---|---|
{
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
|
{
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
|
| C++14 - constexpr | C++20 - constinit |
|---|---|
// 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
|
// 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
|
| C++17 |
|---|
{
// note the semicolon
if (QVariant var = getAnswer(); var.isValid())
use(var);
more_code();
}
|
| C++ | C++20 |
|---|---|
{
T thing = f();
for (auto& x : thing.items()) {
// Note: “for (auto& x : f().items())” is WRONG
mutate(&x);
log(x);
}
}
|
for (T thing = f(); auto& x : thing.items()) {
mutate(&x);
log(x);
}
|
| C++ | C++20 |
|---|---|
{
std::size_t i = 0;
for (const auto& x : foo()) {
bar(x, i);
++i;
}
}
|
for (std::size_t i = 0; const auto& x : foo()) {
bar(x, i);
++i;
}
|
| C++17 | C++20 |
|---|---|
std::printf("%d baskets of %s", n, desc);
//OR
std::cout << n << " baskets of " << desc;
|
std::cout << std::format("{} baskets of {}", n, desc);
|
| whoops! | Apples! 5 baskets left |
|---|---|
std::printf("%s! %d baskets left", n, desc);
|
std::cout <<
std::format("{1}! {0} baskets left", n, desc);
|
| format | result | |
|---|---|---|
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
|
// 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");
|
| C++17 | C++20 |
|---|---|
???
|
std::cout << std::format("The time is {}", std::system_clock::now());
|
| Before C++20 | After C++20 |
|---|---|
typename std::remove_cv<typename std::remove_reference<T>::type>::type
std::remove_cv_t<std::remove_reference_t<T>>
|
typename std::remove_cvref<T>::type
std::remove_cvref_t<T>
|
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'.
}
However, `std::decay
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);
In C++20, the standard library provides the `std::remove_cvref
template
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'.
}
================================================
FILE: spaceship-operator.md
================================================
Better Equality
===============
| Before C++20 | After C++20 |
|---|---|
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);
}
};
|
class Chair
{
int numLegs = 4;
Color color = RGB(128, 128, 128);
Style style;
public:
auto operator==(Chair const &) const = default;
};
|
| Before C++20 | After C++20 |
|---|---|
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);
}
};
|
class MyInt {
int value = 0;
public:
auto operator<=>(MyInt) const = default;
};
|
strcmp/memcmp |
operator<=> |
Meaning |
|---|---|---|
if (strcmp(a, b) < 0);
|
if (auto cmp = a <=> b; cmp < 0);
|
If |
if (strcmp(a, b) > 0);
|
if (auto cmp = a <=> b; cmp > 0);
|
If |
if (strcmp(a, b) == 0);
|
if (auto cmp = a <=> b; cmp == 0);
|
If |
| C++ | C++20 |
|---|---|
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, "");
//...
};
|
auto f = []<typename T>(std::vector<T> vector) {
//...
};
|
| C++ | C++20 |
|---|---|
// 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;
//...
};
|
auto f = []<typename T>(std::vector<T> vector) {
//...
};
|