Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ TL;DR news 2 #26

Closed
GuillaumeBelz opened this issue May 17, 2021 · 11 comments
Closed

C++ TL;DR news 2 #26

GuillaumeBelz opened this issue May 17, 2021 · 11 comments
Labels
aide demandée Une aide supplémentaire est demandée état : fini La proposition a été acceptée et appliquée

Comments

@GuillaumeBelz
Copy link

GuillaumeBelz commented May 17, 2021

Cet revue d'articles sera publiée le dimanche 23 mai. Postez ici vos propositions de revue.

Vous pouvez vous inspirer du premier billet pour rédiger votre revue : https://zestedesavoir.com/billets/3930/c-tl-dr-news-1/. Essayer d'etre clair, concis.

@GuillaumeBelz GuillaumeBelz added aide demandée Une aide supplémentaire est demandée scope : discord état : en discussion La proposition est en cours de discussion labels May 17, 2021
@GuillaumeBelz
Copy link
Author

GuillaumeBelz commented May 17, 2021

quelques lectures prochaines possible :

A noter, ce n'est pas nécessairement des articles publiés cette semaine, mais plus mes lectures de la semaine. Si vous lisez un article publié il y a 2 ans et qu'il est intéressant, ça va aussi,

@LucHermitte
Copy link

J'ai bien aimé ce billet chez redhat il y a quelques semaines https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind (il y en a eu d'autres de sympas peu après).

@GuillaumeBelz
Copy link
Author

Je pourrais mettre aussi dans cette revue les relectures de livres, comme celle que j'ai fait (rapidement) pour Charles : https://guillaumebelz.github.io/critiques.html. C'est une critique basé sur un survol du PDF, mais c'est probablement ok de publier ca.

@robin850
Copy link

Très sympa le concept du billet.

Quelques propositions :

  • Ca n'est pas forcément spécifique au C++ mais je trouve cet article sur le "stack unwinding" très bien expliqué.

  • Ce Google Doc montre énormément d'exemples de subtilités qui existent entre le C et le C++.

  • Ce dépôt contient aussi beaucoup d'algorithmes implémentés en C++.

@GuillaumeBelz
Copy link
Author

A Default Value to Dereference Null Pointers

Lire l'article original : https://www.fluentcpp.com/2021/05/14/a-default-value-to-dereference-null-pointers/

std::optional = nullable object, avec une bonne sémantique pour gérer le cas ou c'est null. optional peut avoir toutes les valeurs de T + nullopt (= not set) sans avoir besoin d'utiliser une valeur spécifique

std::optional<int> f()
{
    if (thereIsAnError) return std::nullopt;

    // happy path now, that returns an int
}

auto result = f();
std::cout << (result ? *result : 42) << '\n';

std::cout << f().value_or(42) << '\n';

Pour les pointeurs, nullable aussi, mais la dernière syntaxe n'est pas utilisable. Comment ajouter value_or pour les pointeurs ?

template<typename T, typename U>
decltype(auto) value_or(T* pointer, U&& defaultValue)
{
    return pointer ? *pointer : std::forward<U>(defaultValue);
}

Le type de retour (rvalue ou lvalue) va dépendre de la catégorie de valeur de la valeur par défaut.

@GuillaumeBelz
Copy link
Author

GuillaumeBelz commented May 24, 2021

C++20 Coroutine: Under The Hood

Lire l'article d'origine : http://www.vishalchovatiya.com/cpp20-coroutine-under-the-hood/

Suite d'un article pour faire des "coroutine" en C, avec les fonctions systèmes. Le lien dans l'article. Quelques notions sont présentées dans ce premier article.

Coroutine : une fonction qui peut être suspendu et reprise. Peut être vu comme intermédiaire entre fonction et thread. Comparé aux threads :

  • pas la lourdeur du contexte de thread, ni du changement de contexte.
  • Et contrairement aux threads, qui peuvent être suspendu à n'importe quel moment par le scheduler du système, les coroutines sont suspendus a un point déterminé du code (donc beaucoup plus simple a gérer la concurrence).

resume

En pratique, qu'est-ce qu'une coroutine en C++20 ? Une fonction qui contient co_await, co_yield et/ou co_return, et peu retourner std::promise.

Du point de vue haut niveau, une coroutine est :

  • un promise, qui contrôle le comportement de la coroutine et sert d'intermédiaire entre le code appelant et le code appelé
  • un awaiter, qui contrôle la suspension et la reprise de la coroutine
  • un coroutine handler, qui controle l'execution

L'article donne des liens vers 2 exemples d'utilisation de coroutines, dans le design pattern Iterator et dans un générateur de séquence d'entiers.

Suspendre une coroutine

struct HelloWorldCoro {
    struct promise_type { // compiler looks for `promise_type`
        HelloWorldCoro get_return_object() { return this; }    
        std::suspend_always initial_suspend() { return {}; }        
        std::suspend_always final_suspend() { return {}; }
    };
    HelloWorldCoro(promise_type* p) : m_handle(std::coroutine_handle<promise_type>::from_promise(*p)) {}
    ~HelloWorldCoro() { m_handle.destroy(); }
    std::coroutine_handle<promise_type>      m_handle;
};

Dans ce code :

  • Promise i.e. promise_type, doit contenir certaines fonctions
  • Awaiter i.e. std::suspend_always
  • Coroutine handle i.e. std::coroutine_handle

Utilisation :

HelloWorldCoro print_hello_world() { // Iigne 1
    std::cout << "Hello "; // ligne 2
    co_await std::suspend_always{}; // ligne 3
    std::cout << "World!" << std::endl; // ligne 4
} // ligne 5

int main() {
    HelloWorldCoro mycoro = print_hello_world(); // ligne A
    mycoro.m_handle.resume(); // ligne B
    mycoro.m_handle.resume(); // ligne C
    return EXIT_SUCCESS; // ligne D
}

Pour résumer ce qu'il se passe, le premier resume affiche Hello , le second affiche World!. Pour entrer plus dans les détails, le flux d'exécution suit les étapes suivantes :

  • à la ligne A dans la fonction main, la fonction print_hello_world est appelée.
  • à la ligne 1 de la fonction print_hello_world, le contexte de la coroutine est créé (HelloWorldCoro) puis la coroutine est suspendue en retournant ce contexte.
  • à la ligne B, l'exécution de la coroutine est reprise à la ligne 2. Le texte "Hello " est affiché.
  • à la ligne 3, la coroutine est de nouveau suspendue.
  • à la ligne C, l'exécution est reprise à la ligne 4 et le texte "World!" est affiché.
  • à la ligne 5, le contexte est détruit et la coroutine se termine.
  • le programme se termine à la ligne D.

L'article entre plus en détail sur les codes intermédiaires qui sont générés lors de la compilation.

Note : l'exécution de la coroutine est suspendue juste après le lancement de celle-ci et la ligne 2 n'est pas appelée avant le premier resume. Cela est dû au fait que la fonction initial_suspend retourne std::suspend_always. Il est possible de changer ce comportement, pour que la ligne 2 soit exécutée dès l'appel à la coroutine, en utilisant le type standard std::suspend_never.

Retourner une valeur depuis une coroutine

Comme une coroutine doit retourner le "promise" pour contrôler le flux d'exécution, il n'est pas possible de retourner directement une valeur, comme pour une fonction normale. Pour cela, il faut co_return et return_value :

struct HelloWorldCoro {
    struct promise_type {
        int m_value;
        void return_value(int val) { m_value = val; }
        ...
    };
};

HelloWorldCoro print_hello_world() {
    std::cout << "Hello ";
    co_await std::suspend_always{ };
    std::cout << "World!" << std::endl;
    co_return -1;
}

int main() {
    HelloWorldCoro mycoro = print_hello_world();
    mycoro.m_handle.resume();
    mycoro.m_handle.resume();
    assert(mycoro.m_handle.promise().m_value == -1);
    return EXIT_SUCCESS;
}

Dans ce code, la valeur de retour est déclarée dans la structure promise_type, avec la fonction return_value. Dans la coroutine, une valeur est retournée par co_return, puis cette valeur est récupérée via le promise mycoro.m_handle.promise().m_value.

Rendre une valeur de Coroutine

La valeur retournée par la coroutine ne peut être modifiée qu'une seule fois, lors de l'appel à co_return, ce qui stop l'exécution de la coroutine. Si la ligne contenant co_return dans le code précédent est déplacée après la ligne suspend_always, le texte "World!" ne sera jamais affiché.

Mais dans un code asynchrone comme celui-ci, il peut être intéressant de retourner une valeur à chaque fois que la coroutine est suspendue. Pour cela, il faut utiliser co_yield et yield_value :

struct HelloWorldCoro {
    struct promise_type {
        int m_val;
        std::suspend_always yield_value(int val) {
            m_val = val; 
            return {};
        }
        ...
    };
};

HelloWorldCoro print_hello_world() {
    std::cout << "Hello ";
    co_yield 1;
    std::cout << "World!" << std::endl;
}

int main() {
    HelloWorldCoro mycoro = print_hello_world();
    mycoro.m_handle.resume();
    assert(mycoro.m_handle.promise().m_val == 1);
    mycoro.m_handle.resume();
    return EXIT_SUCCESS;
}

Contrairement à co_return qui stoppait l'exécution de la coroutine, co_yield suspend simplement la coroutine en retournant une valeur. La coroutine peut être reprise ensuite.

Pour résumer :

Terminologie utilisée avec les coroutine en C++20

  • awaitable : type qui accepte l'opérateur unaire co_await. Dans les codes d'exemple précédant, le type standard std::suspend_always a été utilisé, mais il est possible de déclarer son propre type.
  • awaiter : type qui implemente les fonctions await_ready, await_suspend et await_resume. Dans les codes d'exemple précédant, c'était le type standard std::suspend_always. Il existe aussi le type standard std::suspend_never.
  • promise : type qui implemente le type promise_type, qui contient un certain nombre de fonctions définies.
  • coroutine handle : permet de contrôler l'exécution de la coroutine. Le type standard std::coroutine_handle dans les codes d'exemple précédents.
  • coroutine state (ou context object) : contient les informations du contexte de la coroutine (promise object, paramètres d'appel, variables locales, informations de contrôle de l'exécution), sur la Pile.

Les opérateurs unaires :

  • co_await suspend l'execution et s'appelle avec un "awaiter".
  • co_yield suspend l'execution et retourne une valeur.
  • co_return stop l'exécution et retourne une valeur.

L'article contient plus de détails sur le fonctionnement interne et l'implémentation possible des coroutines en C++20. Si vous voulez entrer dans les profondeurs des coroutines, vous pouvez lire la série d'articles de Raymond Chen : https://devblogs.microsoft.com/oldnewthing/20210504-01/?p=105178

@GuillaumeBelz
Copy link
Author

CPPFrUG 45

Le C++ French User Group organise régulièrement des soirées, avec des présentations et discussions sur le C++. Ce groupe se réunit normalement sur Paris, mais depuis le covid, les soirées sont organisées en ligne.

La prochaine demain est demain (mardi 25 mai). Pour s'inscrire : https://www.meetup.com/fr-FR/User-Group-Cpp-Francophone/events/278281513/

@GuillaumeBelz
Copy link
Author

Function Templates - More Details about Explicit Template Arguments and Concepts

Lire l'article d'origine : http://www.modernescpp.com/index.php/function-templates-more-details

Cet article présente deux nouvelles "règles" de bonne pratique, sur les arguments template (C++17) et les concepts (C++20).

Explicitly Specifying the Template Arguments

Cette "règle" est très simple :

std::vector<int> myVec{1, 2, 3, 4, 5};  // avant C++17
std::vector myVec{1, 2, 3, 4, 5};       // depuis le C++17

Il faut préférer la seconde syntaxe au lieu de la première.

Cette règle peut surprendre, mais elle est en fait logique : elle est l'équivalent de la même règle pour les fonctions, appliquée aux classes. Pour une fonction template, on va généralement préférer la déduction des arguments template selon les arguments de la fonction :

template <typename T>
T max(const T& lhs,const T& rhs);

auto res1 = max<float>(5.5, 6.0);   // non
auto res2 = max(5.5, 6.0);          // oui

Dans le cas d'un overload de fonctions template et non template :

double max(const double& lhs, const double& rhs);

template <typename T>
T max(const T& lhs,const T& rhs);

auto res1 = max(5.5, 6.0);    // (1)
auto res2 = max<>(5.5, 6.0);  // (2)

Ce code n'est pas ambigüe, du fait que la ligne (1), qui peut utiliser les 2 fonctions, va préférer la fonction non template et la ligne (2) ne va considérer r que la fonction template.

Overloading with Concepts

La seconde "règle" consiste simplement à contraindre par défaut les paramètres template en utilisant les concepts.

MyClass max(MyClass lhs, MyClass rhs);

template <std::totally_ordered T>
T max(const T& lhs,const T& rhs)

template <typename T>
T max(const T& lhs,const T& rhs);

auto value2 = max(MyClass{1}, MyClass{2});   // (1)
auto value2 = max(1, 2);                     // (2)

Dans ce code, la ligne (1) n'est pas ambigue, puisqu'elle va préférer la fonction non template. La ligne (2) n'est pas non plus ambigue, puisqu'elle va préférer la fonction template avec contrainte (par le concept std::totally_ordered).

@GuillaumeBelz
Copy link
Author

@Elanis Elanis added état : fini La proposition a été acceptée et appliquée and removed état : en discussion La proposition est en cours de discussion labels May 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aide demandée Une aide supplémentaire est demandée état : fini La proposition a été acceptée et appliquée
Projects
None yet
Development

No branches or pull requests

4 participants