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; 
};
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.
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;  
}
(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
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);   
}
**[[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.
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 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
{
    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 diff = std::chrono::high_resolution_clock::now() - start;
    std::cout << "Time: " << diff.count() << " sec " << std::endl;
}

Output Output
Time: 0.00655365 sec Time: 0.00608944 sec
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.
C++20
bool enoughCoffee(int coffeeLeft) {
    switch(coffeeLeft){
        case 0: return false;
        [[likely]] case 1: return false;
    }
    return true;
}
================================================ FILE: boolean_explicit.md ================================================ _explicit(boolean-expression)_
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))
    {
    }
};

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:
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);
};

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.
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 ls = {3, 2, 1, 5, 4};
    sortContainer(ls);
}

#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  ls = { 1, 2, 3, 4, 5, 6 };
    sortContainer(ls);
}


**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&)'

   27 |     sortContainer(ls);

      |                     ^

source:17:6: note: candidate: 'template  requires  random_access_range 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&)'

   27 |     sortContainer(ls);

      |                     ^

source:17:6: note: candidate: 'template  requires  random_access_range 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
================================================ 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.
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;
}
Immediate functions (specified with consteval) cannot be applied to: - destructors - functions which allocate or deallocate
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 

### 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).
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
================================================ 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:
C++17
{
   // note the semicolon
   if (QVariant var = getAnswer();  var.isValid())
      use(var);
      
   more_code();
}
C++20 adds a similar init, for range-based `for` statements
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);
}

Also helps with "combined" index-y and range-y loops:
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;
}
_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
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);
 
#### Ordering
whoops! Apples! 5 baskets left

std::printf("%s! %d baskets left", n, desc);
 

std::cout <<
   std::format("{1}! {0} baskets left", n, desc);
 
#### 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).
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");


#### Chrono
C++17 C++20

???

 

std::cout << std::format("The time is {}", std::system_clock::now());
 
================================================ FILE: remove-cvref.md ================================================ std::remove_cvref =================== Summary -------
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>
Detail ------ `std::decay` is often (mis)used to obtain the type name for a type that is potentially a reference, cv-qualified, or both:
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` introduces decay semantics for array and function types:
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` type trait to fulfill this purpose without introducing unwanted decay semantics:
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;
};
Spaceship Operator ==================
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;
};
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`:
strcmp/memcmp operator<=> Meaning
if (strcmp(a, b) < 0);
if (auto cmp = a <=> b; cmp < 0);

If a is less than b.

if (strcmp(a, b) > 0);
if (auto cmp = a <=> b; cmp > 0);

If a is greater than b.

if (strcmp(a, b) == 0);
if (auto cmp = a <=> b; cmp == 0);

If a is equal to b.

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.
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) {

   //...
};
Using _types_ inside a generic lambda was hard, verbose, annoying:
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) {

   //...
};
#### 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.