Commit 117a90a8 authored by CHAMONT David's avatar CHAMONT David
Browse files

add files in /tuto

parent 9ba4bd10
......@@ -4,7 +4,7 @@ L’outil Verrou vise à faciliter le diagnostic et la correction des erreurs de
* prérequis : bases du calcul flottant.
* hauteur du plongeoir : 3m.
* préinstallation : Docker, image `piscineri3/calculverrou:1`.
* préinstallation : Docker, image `piscineri3/calculverrou:2`.
* fichiers : https://gitlab.in2p3.fr/MaitresNageurs/PiscineJI/tree/master/CalculVerrou
* d'après : [Francois Fevotte & Bruno Lathuilière](https://github.com/edf-hpc/verrou).
* maître(s) nageur(s) : [David Chamont](http://informatique.in2p3.fr/?q=user/3).
......@@ -17,8 +17,8 @@ avec vos outils d'édition habituels, et d'utiliser une machine Docker pour comp
en combinaison avec Verrou.
Vous devriez avoir déjà installé Docker, conformément aux instructions du [README.md](../README.md) principal de ce projet.
Vous devriez également déjà disposer de l'image `piscineri3/calculverrou:1`. Si ce n'est pas le cas et que vous disposez
de réseau, tapez `docker pull piscineri3/calculverrou:1`.
Vous devriez également déjà disposer de l'image `piscineri3/calculverrou:2`. Si ce n'est pas le cas et que vous disposez
de réseau, tapez `docker pull piscineri3/calculverrou:2`.
En partant de l'hypothèse que vous éditerez vos fichiers dans le répertoire local <LOCAL>, nous vous proposons de monter
ce répertoire en tant que `/work` au sein de la machine Docker.
......@@ -47,10 +47,10 @@ int main()
```
A présent, compilez le et exécutez le au sein d'une machine Docker
`piscineri3/calculverrou:1` :
`piscineri3/calculverrou:2` :
```shell
> docker run --rm -it -v <LOCAL>:/work -w /work piscineri3/calculverrou:1
> docker run --rm -it -v <LOCAL>:/work -w /work piscineri3/calculverrou:2
>> g++ -std=c++14 lost_precision.cpp -o lost_precision.exe
>> ./lost_precision.exe
pi : 3.14159
......@@ -61,7 +61,7 @@ pi+small : 3.143
```
A présent, tentons une exécution "verrouillée" :
```shell
> docker run --rm -it -v <LOCAL>:/work -w /work piscineri3/calculverrou:1
> docker run --rm -it -v <LOCAL>:/work -w /work piscineri3/calculverrou:2
>> source /opt/verrou/1.1.0/env.sh
>> valgrind --tool=verrou --rounding-mode=random ./lost_precision.exe
==72== Verrou, Check floating-point rounding errors
......@@ -117,508 +117,7 @@ Vous etes prêt.
---
## Inférence de type : le mot-clef `auto`
C++ est statiquement et fortement typé : le programmeur doit préciser au
compilateur le type de toutes les variables qu'il utilise, ce qui peut conduire
à un code difficile à écrire comme à lire.
```c++
// Fichier auto_introduction_1.cpp
#include <iostream>
#include <vector>
void affiche( std::vector<int> & col )
{
std::vector<int>::iterator itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
std::vector<int> v {1,2,3,4,5} ;
affiche(v) ;
}
```
Traditionnellement, on allège les notations en ayant recours aux alias
de types :
```c++
// Fichier auto_introduction_2.cpp
#include <iostream>
#include <vector>
typedef std::vector<int> Collection ;
void affiche( Collection & col )
{
Collection::iterator itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
Collection v {1,2,3,4,5} ;
affiche(v) ;
}
```
Cependant, on peut juger inutile de devoir préciser le type de la variable
`itr`, qui est le même que celui de l'expression servant à calculer
sa valeur initiale (`col.begin()`).
Dans ce cas de figure, C++11 permet d'utiliser le mot-clef `auto` à la place du type
de la variable, indiquant ainsi au compilateur de réutiliser le type de la valeur
initiale.
```c++
// Fichier auto_introduction_3.cpp
#include <iostream>
#include <vector>
typedef std::vector<int> Collection ;
void affiche( Collection & col )
{
auto itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
Collection v {1,2,3,4,5} ;
affiche(v) ;
}
```
Attention : lorsque vous utilisez `auto`, évitez d'utiliser en même temps l'initialisation
par accolades introduite par C++11 . Il existe en effet une possibilité d'interaction indésirable
entre ces deux nouveautés (`auto` risque de retenir `std::initializer_list` comme
type). Il est prévu de résoudre ce problème dans la norme C++17.
On notera que `auto`, lorsqu'il est utilisé avec des patrons de fonction, permet
d'implémenter des choses impossibles auparavant :
```c++
// Fichier auto_introduction_4.cpp
#include <iostream>
template< typename LHS, typename RHS >
void affiche_division( LHS lhs, RHS rhs )
{
auto res = lhs/rhs ;
std::cout<<res<<std::endl ;
}
int main()
{
affiche_division(3.,2) ;
}
```
Tant que l'on ne connait pas `T1` et `T2`, impossible de connaitre le type de `lhs/rhs`.
Seul `auto` permet de déclarer `res` avec le type qui préservera toute la précision
utile du calcul.
---
## Formes modifiées de `auto`
Attention, le type déduit par `auto` ne conserve pas certaines caractéristiques du type
de la valeur initiale. Notamment :
* Les modificateurs de référence `&` sont ignorés,
* Les modificateurs `const` de plus haut niveau sont ignorés,
* Les tableaux et fonctions sont traités comme des pointeurs.
Quelques exemples :
```c++
const int a = 3 ;
const int const * p = & a ;
auto b = a ; // b est de type int
auto q = p ; // q est de type const int *
...
int d = 3 ;
int & e = d ;
auto f = e ; // f est une copie de d de type int, et non une référence vers d de type int &
...
int tab[3] ;
auto ptr = tab ; // ptr est de type int *
```
Cette caractéristique est volontaire. En fait, les [règles d'inférence de type]
(http://en.cppreference.com/w/cpp/language/template_argument_deduction#Other_contexts)
sont inspirées de celles utilisées par le compilateur pour déduire
automatiquement les paramètres des fonctions génériques (templates). Elles permettent,
entre autres choses, d'obtenir plus de flexibilité dans l'utilisation d'`auto`. Par exemple,
si le modificateur `const` était conservé, on ne pourrait pas utiliser `auto` pour créer une
variable entière ordinaire à partir d'une constante entière.
Si vous voulez restaurer certaines des caractéristiques perdues lors l'inférence de type, ou
pour créer des types légèrements différents du type original, il est possible d'ajouter des
modificateurs à `auto` :
```c++
const int a = 3 ;
const int const * p = &a ;
const auto b = a ; // b est de type const int
const auto q = p ; // c est de type const int const *
...
int d = 3 ;
int & e = d ;
auto & f = e ; // f est une référence vers d de type int &
const auto & g = e ; // g est une référence de type const int &, ne permettant pas de modifier d
```
---
## Déclaration à droite du type de retour
Une nouvelle façon de déclarer le type de retour s'appuie également sur
le mot-clef `auto`, placé avant la signature de la fonction, et l'opérateur `->`
placé après la signature :
```c++
// Fichier auto_return_1.cpp
#include <iostream>
auto division( double lhs, double rhs ) -> double
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
Certains programmeurs désapprouve le choix du comité de normalisation de
réutiliser le mot-clef `auto` pour ce mécanisme. En effet, il n'y
a ici aucune inférence de type.
---
## Déduction du type de retour
En fait, on peut omettre la partie `-> double`, auquel cas il y a effectivement
inférence : le compilateur cherche la ou les instructions `return` dans le
code, et en détruit le type de retour. Il faut bien sur que toutes ces instrutions
soient de même type.
Ci-dessous, cela nous permet de rétablir l'utilisation de types paramétrés
et l'utilisation d'un type de retour exactement adapté au besoins de
précision :
```c++
// Fichier auto_return_2.cpp
#include <iostream>
template< typename LHS, typename RHS >
auto division( LHS lhs, RHS rhs )
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
---
## Le mot-clé `decltype`
Plutôt que de laisser tout le travail au compilateur, on peut également
récupérer le type d'une expression quelconque grâce au mot-clef `decltype`,
et s'en servir pour déclarer une nouvelle variable :
```c++
int n ;
double x ;
decltype(2*x+n) z ; // z sera du type de 2*x+n, soit ici double
.....
std::map<int,std::string> m1 ;
decltype(m1) m2 ; // m2 aura le même type que m1 ;
```
En réalité, `decltype` est utilisable partout ou le compilateur attend un nom
de type, par exemple dans la définition d'un alias :
```c++
// Fichier auto_decltype_1.cpp
#include <iostream>
template< typename LHS, typename RHS >
void affiche_division( LHS lhs, RHS rhs )
{
typedef decltype(lhs/rhs) Resultat ;
Resultat res = lhs/rhs ;
std::cout<<res<<std::endl ;
}
int main()
{
affiche_division(3.,2) ;
}
```
Ou encore :
```c++
// Fichier auto_decltype_2.cpp
#include <iostream>
template< typename LHS, typename RHS >
auto division( LHS lhs, RHS rhs ) -> decltype(lhs/rhs)
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
Dans l'exemple ci-dessus, nous n'aurions pas pu placer `decltype(lhs/rhs)`
à la place de `auto`, à la gauche du nom de fonction, car à ce stade
les arguments `lhs` et `rhs` ne sont pas encore connus. Il est donc
nécessaire d'utiliser la déclaration à droite du type de retour...
ou ne rien déclarer du tout, car le compilateur peut déduire le
type de retour à partir de l'instruction `return`.
---
## `decltype` n'est pas `auto`
Attention : pour conclure avec le mot-clef `decltype`, notons qu'
**il n'est pas complètement équivalent** à `auto`. En effet il prend
l'exact type de l'expression passée en argument, sans retirer aucun
`const` et aucun `&`.
Si vous avez envie d'utiliser l'inférence de type, mais sans toucher
au type original d'aucune manière... utilisez `decltype(auto)` à la
place de `auto`.
---
## Afficher le type d'une variable
Lorsqu'interviennent `auto` et `decltype`, hors des cas triviaux,
il peut devenir difficile de connaitre à coup sûr le type d'une
variable. Comment s'en assurer ?
Pour soutirer certaines informations à un compilateur C++, il est courant
de lui fournir une instruction volontairement erronée, écrite de telle sorte
que le message d'erreur contiennent l'information recherchée.
Ci-dessous, nous déclarons un patron de classe qui n'est jamais défini, ce qui permet
indirectement d'en savoir plus sur le type déduit par auto pour x et y :
```c++
// Fichier auto_td_1.cpp
template <typename T> ;
class TypeDisplayer ;
int main()
{
const int the_answer = 42 ;
auto x = the_answer ;
auto y = & the_answer ;
TypeDisplayer<decltype(x)> x_type ; // errors containing
TypeDisplayer<decltype(y)> y_type ; // x's and y's types
}
```
Par ailleurs, certains outils d'édition de code tentent de vous informer de
façon interactive sur les types associés aux utilisations de `auto`
dans votre code, avec plus ou moins de succès.
Vous pourriez être tentés d'utiliser `typeid` et `std::type_info::name`, mais ces fonctions
sont requises d'éliminer les références, les const, et présentent souvent leur résultat
sous une forme mutilée (mangled).
Si vous voulez mettre des instructions d'affichage de type dans votre code, la piste la plus
aboutie est sans doute la bibliothèque Boost TypeIndex.
---
## Un risque d'utilisation excessive
L'utilisation de l'inférence de type apporte beaucoup de bénéfices :
* une écriture simplifiée,
* un algorithme plus lisible,
* une protection contre les fautes de frappes dans les noms de types,
* une plus grande généricité, donc un remaniement et une évolution plus facile du code.
Devant le succès et l'efficacité d'`auto`, le comité C++ a cherché d'élargir autant que possible ses
usages possibles. Cependant, nous conclurons sur une mise en garde : tout à la joie de ne plus avoir à expliciter
tous les types, on peut couvrir son code d'`auto`, même lorsque le type d'une variable est parfaitement
connu, déterministe et rapide à taper. Ceci pour tendre vers un style de programmation ou tous les types
sont déduits par le compilateur.
Il faut être bien conscient que le code résultant peut devenir moins lisible, car il y est plus difficile
de visualiser le type d'une variable, donc de voir et corriger les erreurs de type. De plus, l'utilisation
excessive d'`auto` peut briser une abstraction, en amenant un compilateur à accepter un objet d'un type
incorrect dans l'initialisation d'une variable, ou comme paramètre d'une fonction, pour ne s'en plaindre
que plus tard (lorsque le code en fait un usage incohérent).
Enfin, le mot-clé `auto` peut apparaitre dans de nombreux contextes, chacun utilisant des règles
d'inférence subtilement différentes et complexes, par exemple lors de la combinaison avec l'initialisation
par accolades. Il est aisé de commettre une erreur dès que l'on sort du cas trivial d'une nouvelle
variable avec une valeur initiale.
En toute chose, mesure est bonne...
---
## Jeu
Dans toutes les questions ci-dessous, nous vous invitons à deviner le type de la
variable `x`, ou bien le type `X`, et à vérifier votre réponse en utilisant la technique
de "Type Displayer" décrite ci-dessus.
Nous commencons par les cas triviaux et les usages simples et "sains". Nous
terminons par les cas complexes et les pièges, afin de vous invitez à la prudence
et à éviter un usage "excessif" de ce nouveau jouet.
**Question 1**
```c++
const int the_answer = 42 ;
auto x = the_answer ;
```
**Question 2**
```c++
const int the_answer = 42 ;
const int * px = &the_answer ;
auto x = px ;
```
**Question 3**
```c++
const int the_answer = 42 ;
const int & the_other_answer = the_answer ;
auto x = the_other_answer ;
```
**Question 4**
```c++
const int the_answer = 42 ;
const int & the_other_answer = the_answer ;
const auto & x = the_other_answer ;
```
**Question 5**
```c++
const int the_answer = 42 ;
decltype(the_answer) x = the_answer ;
```
**Question 6**
```c++
const int the_answer = 42 ;
decltype(auto) x = the_answer ;
```
**Question 7**
```c++
const int the_answer = 42 ;
const int & the_other_answer = the_answer ;
decltype(auto) x = the_other_answer ;
```
**Question 8**
```c++
template< typename LHS, typename RHS >
auto division( LHS lhs, RHS rhs ) -> decltype(lhs/rhs)
{ return lhs/rhs ; }
const auto x = division(3.,2) ;
```
**Question 9**
```c++
#include <vector>
const std::vector<int> v {1,2,3,4,5} ;
auto x = v.begin() ;
```
**Question 10**
```c++
#include <vector>
const std::vector<int> v {1,2,3,4,5} ;
decltype(auto) x = *v.begin() ;
```
**Question 11**
```c++
#include <vector>
std::vector<int> v {1,2,3,4,5} ;
auto x { v } ;
```
**Question 12**
```c++
#include <vector>
std::vector<int> v {42} ;
auto x = { 42 } ;
```
**Question 13**
```c++
const int the_answer = 42 ;
const int & the_other_answer = the_answer ;
auto & x = the_other_answer ;
```
**Question 14**
```c++
struct TheAnswer
{
public : TheAnswer() : value(42) {}
private : int value ;
} ;
const TheAnswer * p = new TheAnswer ;
decltype(auto) x = p->value ;
```
**Question 15**
```c++
struct TheAnswer
{
public : TheAnswer() : value(42) {}
private : int value ;
} ;
const TheAnswer * p = new TheAnswer ;
decltype(auto) x = (p->value) ;
```
**Question 16**
```c++
template< typename LHS, typename RHS >
auto min( LHS lhs, RHS rhs ) -> decltype(lhs<rhs?lhs:rhs)
{ return lhs<rhs?lhs:rhs ; }
decltype(auto) x = min(3.,2.) ;
```
## En Travaux...
---
......@@ -637,14 +136,13 @@ Merci d'envoyer quelques commentaires à l'auteur :
---
## Sources
* [Cpp Reference](http://en.cppreference.com/w/cpp/language/auto)
* [Thomas Becker](http://thbecker.net/articles/auto_and_decltype/section_01.html)
* [Alex Allain](http://www.cprogramming.com/c++11/c++11-auto-decltype-return-value-after-function.html)
* [Zeste de savoir](https://zestedesavoir.com/articles/28/le-c-14-est-arrive/#3-les-nouvelles-fonctionnalites-sur-les-fonctions)
* [HyHg](https://bitbucket.org/hyhg/revisions/wiki/Home)
* [Présentation de Verrou à l'Inria](https://sed.saclay.inria.fr/demandez-le-programme/presentations/2017-10-17.pdf)
* [Publication HAL](https://hal.archives-ouvertes.fr/hal-01383417/)
* [Sources sur GitHub](https://github.com/edf-hpc/verrou)
* [Documentation](https://edf-hpc.github.io/verrou/vr-manual.html)
---
© *CNRS 2016*
*Assemblé et rédigé par David Chamont, cette œuvre est mise à disposition selon les termes de la*
© *CNRS 2017*
*Assemblé par David Chamont, cette œuvre est mise à disposition selon les termes de la*
*[Licence Creative Commons - Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/)*
......@@ -12,6 +12,8 @@ ARG VALGRIND=3.13.0
ARG VERROU=1.1.0
ARG PREFIX=/opt/verrou/1.1.0
WORKDIR /tuto
RUN wget https://github.com/edf-hpc/verrou/releases/download/v${VERROU}/valgrind-${VALGRIND}.verrou-${VERROU}.tar.bz2 -O valgrind-${VALGRIND}.verrou-${VERROU}.tar.bz2 \
&& tar -xvf valgrind-${VALGRIND}.verrou-${VERROU}.tar.bz2 \
&& rm -rf valgrind-${VALGRIND}.verrou-${VERROU}.tar.bz2
......@@ -22,3 +24,4 @@ RUN cd valgrind-${VALGRIND}+verrou-${VERROU} \
&& ./autogen.sh && ./configure --enable-only64bit --prefix=${PREFIX} --enable-verrou-fma=no \
&& make && make install
COPY src src
#!/bin/bash
docker build --no-cache -t piscineri3/calculverrou:1 . | tee docker_build.log
cp -r ../src src
#docker build --no-cache -t piscineri3/calculverrou:2 . | tee buildall.log
docker build -t piscineri3/calculverrou:2 . | tee build.log
#!/bin/bash
docker run -it --rm -v $PWD:/work -w /work piscineri3/calculverrou:1
docker run -it --rm -v $PWD:/work -w /work piscineri3/calculverrou:2
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment