Xavier Lamorlette

C++ 20

Syntaxe

Constexpr Virtual Methods

On peut désormais utiliser des méthodes virtuelles dans des expresssions constantes.

Designated Initialisers

class My_class {
public:
    int a = 0;
    int b = 0;
    int c = 0;
};

{
    My_class object{.a = 1, .c = 3};
}

Lambda Templates

auto foo = []<typename T>(const std::vector<T> & vector_of_stuff) {
        …
    };

Spaceship <=> operator

Cet opérateur doit être une relation d'ordre total (weak ordering), c'est-à-dire vérifiant (a == b || a < b || a > b) == true. == signifie ici que les éléments sont équivalents mais pas forcément identiques.

Avec une relation d'ordre strict total (strict weak ordering), == signifie que les éléments sont identiques.

Cet opérateur peut être construit automatiquement :

auto operator<=>(const A &) = default;

Attributs likely et unlikely

if (condition) [[likely]] {
   …
} else [[unlikely]] {
   …
}

Concepts

Les concepts permettent de définir sémantiquement les contraintes de validité des paramètres templates.
Ce sont des prédicats booléens, nommés, sur des paramètres templates, évalués à la compilation.

La surcharge avec des concepts permet de remplacer les traits et les enable_if.
Les concepts peuvent être utilisés pour la résolution de surcharge au lieu du SFINAE.

Exemples :

template <typename T>
concept Integral = std::is_integral<T>::value;

template <typename T>
concept EqualityComparable  =
  requires(T a, T b) {
    {a == b} -> std::same_as<bool>;
    {a != b} -> std::same_as<bool>;
  };

template <EqualityComparable T>
bool are_equal(const T & a,
        const T & b);
    return a == b;
}

Remarque : les concepts ne contenant qu'une seule propriété ou contrainte sont suspects.

Une expression requires teste si les paramètres templates fournissent les interfaces désirées, et renvoie un booléen :

template <typename Iterator>
struct Foo {
  static_assert(requires(Iterator i) {i ++;}, "template parameter does not provide increment");
}

Fonction template contrainte par un concept :
(L'utilisation d'un concept dans la signature d'une fonction rend cette fonction template.)

void foo(Integral a);
    // ...
}

Les concepts peuvent être utilisés au lieu du susbtitut non contraint auto :

Sortable s = f(x);  // au lieu de auto s = f(x);

requires peut être mis avant ou après le nom :

template <typename T>
requires Integral<T>()
T foo(T x) {
  …
}

template <typename T>
T foo(T x)
requires Integral<T>() {
  …
}

Les syntaxes abrégées sont équivalentes :

template <Integral T>
T foo(T x) {
  …
}

Integral auto foo(Integral auto x) {
  …
}

Regular et SemiRegular

SemiRegular est un concept équivalent à DefaultConstructible + Destructible + CopyConstructible + CopyAssignable + MoveConstructible + MoveAssignable + Swappable.

Regular est un concept équivalent à SemiRegular + EqualityComparable.

Les containeurs et les algorithmes de la STL requièrent des types Regular.

Coroutines

Les coroutines sont des fonctions asynchrones, c'est-à-dire qui peuvent suspendre et reprendre leur exécution tout en conservant leur état.

co_yield suspend l'exécution d'une fonction et retourne une valeur. Cela permet d'écrire des générateurs, avec évaluation retardée (appel par nécessité, “lazy evaluation”) :

std::generator arithmetic_suite(int first_element,
        int difference) {
    for (int element = first_element; ; element += difference) {
        co_yield element;
    }
}

co_return permet de retourner une valeur depuis une coroutine.

co_await permet de suspendre et reprendre l'exécution en fonction d'une expression qui doit implémenter :

Modules

// module interface: my_module.ixx
module;

import std.core;

export module my_module;

export namespace my_module {
int foo(int arg);
}
// module implementation: my_module.cpp
module my_module;

import std.core;

namespace my_module {

int foo(int arg) {
  …
}

}
// client code
import std.core;

import my_module;

{
    my_module::foo(1);
}

Le module std.core contient quasiment toute la STL (notamment sauf std.memory).

Ranges

La bibliothèque Ranges permet d'appliquer directement des algorithmes sur des collections, sans passer par des itérateurs, dans un style de programmation fonctionnelle.
On peut composer les algorithmes grâce à l'opérateur pipe |.
On peut aussi appliquer les algorithmes sur des flux infinis de données (streams ou pipelines).

Un std::range est un groupe d'éléments sur lequel on peut itérer.
Une std::view est adaptation composable d'un range, qui ne possède pas les données. La fonction d'adaptation est appliquée lorsque l'on itère sur la view.

Exemples d'utilisation :

const std::string_view value;

size_t length = std::ranges::distance(value);

std::ranges::count(value, 'x');

auto sub_string = std::ranges::subrange(std::ranges::find(value, 'x') + 1, std::ranges::end(value));

std::ranges::all_of(value, predicate);

std::vector<int> numbers = {1, 2, 3, 4};
std::vector<int> incremented_odd_numbers = numbers
    | std::views::filter([](int n){return n % 2 == 1;})
    | std::views::transform([](int n){ return n + 1;});

auto result = collection
    | std::views::drop_while(predicate)
    | std::views::take_while(predicate_2);

STL

std::format

std::format permet de formatter les chaînes de caractères.
Cela remplace notamment l'utilisation de iomanip.

std::span

std::span est une vue sur une séquence contigue d'objets.
Il n'est pas propriétaire de la mémoire. La mémoire contigue peut-être, par exemple, celle d'un tableau C, d'un array on d'un vector.

Containeurs constexpr

std::string et std::vector peuvent être constexpr.

La dernière mise à jour de cette page date de mai 2020.

Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.