Closed GuillaumeBelz closed 3 years ago
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,
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).
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.
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++.
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
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.
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 :
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 :
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.
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 :
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 :
main
, la fonction print_hello_world
est appelée.print_hello_world
, le contexte de la coroutine est créé (HelloWorldCoro
) puis la coroutine est suspendue en retournant ce contexte.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
.
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
.
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 :
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.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 contient un certain nombre de fonctions définies.std::coroutine_handle
dans les codes d'exemple précédents.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
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/
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).
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.
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
).
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.