Commit 7de109ef authored by CHAMONT David's avatar CHAMONT David
Browse files

create CalculVerrou

parent aef4dd65
FROM piscineri3/calculflottant
ADD . /work
WORKDIR /work
# Inférence de type
C++ est statiquement et fortement typé : le programmeur doit préciser au compilateur
le type de toutes les variables qu'il utilise. C'est généralement considéré comme nécessaire
pour garantir le bon fonctionnement des programmes de très grande taille, mais cela peut
conduire à un code difficile à écrire comme à lire. Pour alléger la tâche du
programmeur sans renoncer au typage, C++11 autorise le compilateur à deviner
certains types dans les cas les moins ambigus. Ce mécanisme, appelé **inférence de type**,
s'appuie en C++ sur le mot-clef `auto`.
* prérequis : connaissance du C++ historique.
* hauteur du plongeoir : 5m.
* préinstallation : Docker, image `piscineri3/gcc61:3`.
* fichiers : https://gitlab.in2p3.fr/MaitresNageurs/PiscineJI/tree/master/CppAuto
* maître(s) nageur(s) : [David Chamont](http://informatique.in2p3.fr/?q=user/3).
* <img src="img/david.jpeg" height=50>
---
## Vérification de votre environnement de travail
Nous vous proposons de modifier vos fichiers de code au sein du système
d'exploitation natif de votre ordinateur portable, avec vos outils d'édition
habituels, et d'utiliser une machine Docker uniquement pour compiler et exécuter
le programme résultant.
Vous devriez avoir déjà installé Docker et VirtualBox, conformément aux instructions
du [README.md](../README.md) principal de ce projet. Vous devriez également déjà
disposer de l'image `piscineri3/gcc61:3`. Si ce n'est pas le cas et que vous disposez
de réseau, tapez `docker pull piscineri3/gcc61:3`.
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
`/piscine` au sein de la machine Docker.
Créez le programme suivant au sein de <LOCAL> :
```c++
// Fichier hello_world.cpp
#include <iostream>
int main()
{
std::cout<<"Hello World !"<<std::endl ;
}
```
A présent, compilez le et exécutez le au sein d'une machine Docker
`piscineri3/gcc61:3` :
```shell
> docker run --rm -it -v <LOCAL>:/piscine piscineri3/gcc61:3 /bin/bash
>> cd /piscine
>> g++ -std=c++14 hello_world.cpp -o hello_world.exe
>> ./hello_world.exe
Hello World !
>> exit
```
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.) ;
```
---
## Sortie de bain
Merci d'envoyer quelques commentaires à l'auteur :
* Environnement de travail opérationnel ? oui [ ], non [ ]
* Vous aviez les pre-requis ? oui [ ], non [ ]
* Comment jugez-vous la difficulté du plongeon ? trop simple [ ], adapté [ ], trop compliqué [ ]
* Durée du plongeon ? trop court [ ], 15-20 minutes [ ], trop long [ ]
* Pourquoi avez-vous choisi ce plongeon ? :
* Signalement de typos, erreurs, etc :
* Commentaires libres :
---
## 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)
---
© *CNRS 2016*
*Assemblé et rédigé 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/)*
#!/bin/bash
docker build -t piscineri3/calculcadna .
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