The New ISO Standard C++20

Bernd Doser

HITS gGmbH

July 2022

This presentation is available at

https://bernddoser.github.io/workshop-cpp20

Agenda

  • Warm-up with C++
  • Important C++11 features
  • C++20 features
    • Concepts, ranges
    • Modules, coroutines
    • std::format, three-way comparison
  • Coding competition

My Background

Why C++?

  • High performance
  • Multi-paradigm
    • Imperative
    • Object-oriented
    • Generic
    • Functional
  • Strong type system
  • Strict backward compatibility

ISO Standardization

  • C++98: First ISO Standard
  • C++11: Smart pointers, move sematic, Lambda functions, range-based for loop, auto
  • C++14: Variable templates, generic lambdas
  • C++17: Structured bindings
  • C++20: Modules, concepts, coroutines, ranges
  • Suggestions for C++23: “A plan for ranges”, reflections

ISO Timeline

Books and ISO Standard

C++98 Warm-up

class Matrix
{
public:

    Matrix(int m, int n)
     : m(m), n(n), mat(m*n)
    {}

    Matrix operator * (Matrix B) {
        Matrix C(this->m, B.n);
        /* C(i,j) = sum_k A(i,k) * B(k,j) */
        return C;
    }

private:
    int m,n;
    std::vector<double> mat;
};

Task: Find the performance issue.

Carbage Collection vs. RAII

Carbage Collection (Java, Python, Go)

  • Automatic memory management, which periodically stops all threads and frees unreferenced memory.
  • Extra overhead

RAII: Resource Aquisition is Initialization (C++, Rust)

  • The lifetime of a resource will be bound to the lifetime of a local variable. C++ automatically manages the lifetime of locals.
  • Smart pointers (C++11) overcome the manually memory deallocation.

C++ principle: “Don’t pay for something you don’t use.”

Raw pointers (before C++11)

  • Manual allocation and deallocation of memory
    • Segmentation faults
    • Memory leaks
void func()
{
    int* valuePtr = new int(42);
    if ( /* ... */ ) return; // memory leak
    delete valuePtr;
}

Note

Raw pointers (new) are deprecated in C++20 and will be removed in C++23.

Smart pointers (C++11)

#include <memory>

void func()
{
    std::shared_ptr<int> ptr (new int(42));

    if ( /* ... */ )
        return;   // no memory leak
}

Better:

std::shared_ptr<int> ptr = std::make_shared<int>();

Best:

auto ptr = std::make_shared<int>();

Smart pointers (C++11)

std::unique_ptr<T>

  • Allows exactly one owner of the underlying pointer
  • Can be moved to a new owner, but not copied or shared

std::shared_ptr<T>

  • Reference-counted smart pointer
  • The raw pointer is not deleted until all owners have gone out of scope

std::weak_ptr<T>

  • Required to break circular references between shared_ptr

Smart pointers (C++11)

    auto p1 = std::make_shared<int>(1);
    auto p2 = p1;
    assert(*p2 == 1);
    assert(p1.use_count() == 2);

    auto p3 = std::make_unique<int>(2);
    // auto p4 = p3;   // unique_ptr can't be copied
    auto p5 = std::move(p3);
    assert(*p5 == 2);
    assert(p3 == nullptr);

    // auto p6 = std::weak_ptr<int>(3);  // weak_ptr can't own
    auto p6 = std::weak_ptr<int>(p1);
    assert(p6.use_count() == 2);
    p1.reset();
    p2.reset();
    assert(p6.expired());

Exercise

Move Semantics (C++11)

L-values will be copied

  • Permanent objects with a name and a storage address
  • Copy is expensive

R-values will be moved

  • Temporary objects without a name and a storage address
  • R-values can only appear on the right side in an assignment
  • Move is fast

Exersice

Containers

Name Description
array static contiguous array
vector dynamic contiguous array
list linked list
set sorted collection of unique keys
map sorted collection of key-value pairs
unordered_set hashed collection of unique keys
unordered_map hashed collection of key-value pairs

C++20: New Keywords

  • Concepts
    • concept
    • requires
  • Coroutines
    • co_await
    • co_return
    • co_yield
  • Modules
    • import
    • module
  • Others
    • constinit
    • consteval
    • char8_t

Modules

  • Modules are a new way to organize C++ code into logical components
  • Problems of the obsolete header files
    • Repetitive compilation of the same code
    • Inclusion order-dependent headers
    • Cyclic dependencies
    • Macros leakage in and out from headers
    • Poor encapsulation of implementation details

Modules

// math.cppm
export module math;

export int add(int fir, int sec)
{
    return fir + sec;
}

// client.cpp
import math;

int main()
{
   add(2000, 20);
}
  • Modules are orthogonal to namespaces.

Coroutines: Evolution of Functions

int f1() { return 1; } // A C-like function

int f2(int arg) { return arg; } // Function overloading
double f2(double arg) { return arg; }

template <typename T> // Function template
T f3(T arg) { return arg; }

struct F4 { // Functor
    int operator()() { return 4; }
};

auto f5 = [] (int i) { return i * i; }; // Lambda (C++11)

auto f6 = [] (auto arg) { // Generic lambda (C++14)
   return std::to_string(arg);
};

Coroutines

  • A coroutine is a generalisation of a function that allows the function to be suspended and then later resumed.
  • A coroutine is stackless: their state is stored in heap, not on stack
  • A coroutine contains one of these keywords:
    • co_return (coroutine return statement)
    • co_await (await expression)
    • co_yield (yield expression)

Coroutines: CppCoro

  • Coroutines provides a very low-level interface.
  • CppCoro provides asynchronous programming abstractions (maybe in C++23)
    • Task<T>: asynchronous computation that is executed lazily
    • Generator<T>: a coroutine type that produces a sequence of values of type T where values are produced lazily and synchronously.

Coroutines: Generator

Generator<int> getNext(int start = 0, int step = 1) {
    auto value = start;
    while (true) {
        co_yield value;
        value += step;
    }
}

auto gen = getNext(100, -10);
for (int i = 0; i <= 20; ++i) {
    gen.next();
    std::cout << gen.getValue() << " ";
}

Concepts

  • With concepts, failure happens early and the error message is much more meaningful.
  • Before C++20 enable_if was used to check template arguments (Example).
// C++11
template <typename T>  // Substitution failure is not an error (SFINAE)
std::enable_if_t<std::is_same_v<T, int>> f(T x);

// C++20
void f(std::same_as<int> auto x);

Concepts: Example

  • Definition
template <typename T>
concept Incrementable = requires(T x) { x++; ++x; };
  • Usage
template <Incrementable T>
void inc(T t);

template <Incrementable T>
void inc(T t) requires Incrementable<T>;

void inc(Incrementable auto t);

The World Before Ranges (C++11)

#include <algorithm>

int main()
{
    std::vector<int> data{42, 1, 12, -3, 14, -5};
    std::vector<int> pos;

    // copy only positive numbers:
    std::copy_if(data.begin(), data.end(),
      std::back_inserter(pos), [](int i){ return i >= 0; });
}

cppreference std::copy_if

template <class InputIt, class OutputIt, class UnaryPredicate>
OutputIt copy_if( InputIt first, InputIt last,
                  OutputIt d_first,
                  UnaryPredicate pred );

Exercise

Ranges: What We Want

  • Direct usage of containers without iterators (pointers)
std::vector<int> v{1,2,3};
std::sort(std::begin(v), std::end(v));
std::ranges::sort(v);
  • Chaining similar to unix pipes
result = data | func1 | func2 | ...
  • Lazy evaluation

Ranges: filter

#include <ranges>
#include <vector>

int main()
{
    std::vector<int> data{42, 1, 12, -3, 14, -5};
    auto even = [](int i){return i >= 0;};

    auto pos = data | std::views::filter(even);
}

Exercise

Ranges: How it works

  • A range is a concept, not a container
  • Defined in header <ranges>:
template < class T >
concept range = requires( T& t ) {
  ranges::begin(t);
  ranges::end(t);
};

Note

Why std::begin(t) instead of t.begin()?

Ranges: Pipelining

C(B)
B | C
auto const data = {0, 1, 2, 3, 4, 5};
auto even = [](const auto& v) { return 0 == v % 2; };
auto square = [](const auto& v) { return v * v; };

// "pipe" syntax of composing the views:
auto result { data
    | std::views::filter(even)
    | std::views::transform(square)
    | std::views::drop(2)
    | std::views::reverse
    | std::views::transform([](const auto& v){ return std::to_string(v); }) };

Ranges: views

Eric Niebler

“Views are composable adaptations of ranges where the adaptation happens lazily as the view is iterated.”

template<class D>
  requires std::is_class_v<D> && std::same_as<D, std::remove_cv_t<D>>
class view_interface;

view_interface is typically used with CRTP

class my_view : public std::ranges::view_interface<my_view> {
public:
    auto begin() const { /*...*/ }
    auto end() const { /*...*/ }
};

Ranges: Lazy Evaluation

auto odd = [](int i){ return i % 2 == 1; };
auto isPrime = [](int i) {
    for (int j=2; j*j <= i; ++j){
        if (i % j == 0) return false;
    }
    return true;
};

for (int i: std::views::iota(1) | std::views::filter(odd)
                                | std::views::filter(isPrime)
                                | std::views::take(10))
{
    std::cout << i << std::endl;
}

C++20: Text Formating

  • std::format
  • Safe and extensible
  • Faster then printf and iostreams
  • Separation of format string and arguments
std::format("The answer is {}.", 42);
std::format("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
  • Available at {fmt} until compiler support

Exercise

Comparison before C++20

  • 6 comparison operators
    • ==, < contain the real logic
    • !=, >, <=, >= will be derived
  • Free functions to allow comparison of convertible types
  • 18 comparison operators for non-convertible types
    • op(const& T1, const& T2)
    • op(const& T2, const& T1)

Exercise

Comparison before C++20

struct Point
{
    int x,y;

    friend bool operator == (const Point& a, const Point& b) {
      return a.x == b.x and a.y == b.y; }
    friend bool operator <  (const Point& a, const Point& b) {
      return a.x < b.x or (a.x == b.x and a.y < b.y); }
    friend bool operator != (const Point& a, const Point& b) {
      return !(a==b); }
    friend bool operator <= (const Point& a, const Point& b) {
      return !(b<a); }
    friend bool operator >  (const Point& a, const Point& b) {
      return b<a; }
    friend bool operator >= (const Point& a, const Point& b) {
      return !(a<b); }
};

C++20: Three-way Comparison

  • Spaceship operator <=> (similar to strcmp)
    • (a <=> b) < 0 if a < b
    • (a <=> b) > 0 if a > b
    • (a <=> b) == 0 if a == b
  • Compiler generates all 6 comparison operators to compare X with Y memberwise
auto X:: operator<=>(const Y&) const = default;
  • Custom implementation needs also operator ==

Comparison Categories

  • strong_ordering
    • exactly one of <, >, == must be true and if a == b then f(a) == f(b)
  • weak_ordering (equivalent but not equal)
    • exactly one of <, >, == must be true and if a == b then f(a) != f(b)
    • Example: CaseInsensitiveString storing original string, but compare in case-insensitive way.
  • partial_ordering
    • one of <, >, == might be true and if a==b then f(a) != f(b)
    • Example: float/double because NaN is not comparable

Coding Competition

GoogleCodeJam

2016 Quali Round Problem B: Revenge of the Pancakes

Stack of pancakes with a happy face made of chocolate on one side ‘+’ and nothing on the other side ‘-’. Goal is to have all pancakes with the happy side on the top.

Here we go!

Coding Competition Solution

std::string stack{"++--+-"};

auto flip = [](const char c){
  return c == '+' ? '-' : '+';   // Conditional/Ternary operator
};

int n = 0;
for (auto first = std::ranges::find(stack, '-'); first != std::end(stack);
     first = std::ranges::find(stack, '-'), n++)
{
  if (stack[0] == '+') {
    auto sub = std::ranges::subrange(std::begin(stack), first);
    std::ranges::transform(sub, std::begin(sub), flip);
  } else {
    auto first_happy = std::ranges::find(stack, '+');
    auto sub = std::ranges::subrange(std::begin(stack), first_happy);
    std::ranges::transform(sub, std::begin(sub), flip);
  }
}
std::cout << "You need " << n << " flips to make all happy." << std::endl;

Exercise

Easier Exercise

  • Print the first 10 prime numbers
auto isPrime = [](int i) {
    for (int j=2; j*j <= i; ++j){
        if (i % j == 0) return false;
    }
    return true;
};

auto primes = // use std::views::iota, std::views::filter, std::views::take(10)

fmt::print("{}\n", primes);

Here we go!

Solution

Summary

  • C++20 is like C++11 a major change in the language
  • The Big Four of C++20
    • Ranges is a great step towards functional programming
    • Modules simplifies multiple translation units
    • Concepts allows template arguments specifications
    • Coroutines are stackless and can be suspended
  • Reflections and more ranges are expected for C++23

Thank you

Bjarne Stroustrup

“Within C++, there is a much smaller and cleaner language struggling to get out”