Outils d'utilisateurs

Outils du Site


genericite_concepts_et_c_14

Différences

Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.

Lien vers cette vue

genericite_concepts_et_c_14 [2014/03/19 09:14]
gbdivers créée
genericite_concepts_et_c_14 [2014/04/20 20:41] (Version actuelle)
87.100.112.252
Ligne 5: Ligne 5:
 ===== Généricité ===== ===== Généricité =====
  
-Le premier code d’exemple du tutoriel de Flob90 se base sur la fonction advance. Celle-ci permet d’incrémenter une variable ayant une sémantique d’itérateur de N éléments dans un conteneur de séquences. Pour rappel, un conteneur de séquences est une collection ordonnée d’éléments qui définie un premier et un dernier élément. Par exemple, un tableau ou une liste chaînée sont des conteneurs de séquences, alors qu’un graph n’en est pas un. Un itérateur (''InputIterator'') peut être décrit comme une forme de pointeur, qui référence un élément et qui permet de passer à l’élément suivant dans le conteneur.+Le premier code d’exemple du tutoriel de Flob90 se base sur la fonction advance. Celle-ci permet d’incrémenter une variable ayant une sémantique d’itérateur de N éléments dans un conteneur de séquences. Pour rappel, un conteneur de séquences est une collection ordonnée d’éléments qui définit un premier et un dernier élément. Par exemple, un tableau ou une liste chaînée sont des conteneurs de séquences, alors qu’un graphe n’en est pas un. Un itérateur (''InputIterator'') peut être décrit comme une forme de pointeur, qui référence un élément et qui permet de passer à l’élément suivant dans le conteneur.
  
 Imaginons le cas d’utilisation suivant : vous avez un tableau d’entiers (''std::vector<int>'') et vous souhaitez déplacer un itérateur de N éléments. Un exemple d’implémentation est de créer une boucle qui incrémente N fois l’itérateur : Imaginons le cas d’utilisation suivant : vous avez un tableau d’entiers (''std::vector<int>'') et vous souhaitez déplacer un itérateur de N éléments. Un exemple d’implémentation est de créer une boucle qui incrémente N fois l’itérateur :
Ligne 39: Ligne 39:
 </code> </code>
  
-Lors de la compilation, le type ''T'' sera remplacé par les types réellement utilisés et le compilateur génère une fonction ''advance'' pour chaque type utilisé.+Lors de la compilation, le type ''T'' sera remplacé par les types réellement utilisés et le compilateur générera une fonction ''advance'' pour chaque type utilisé.
  
 Remarque : une amélioration possible d’implémentation est de surcharger la fonction pour les conteneurs à accès aléatoire, qui autorisent d’écrire directement ''it+=N''. Ce type d’approche sort du cadre de cet article, le lecteur intéressé pourra rechercher "tag dispatching". Remarque : une amélioration possible d’implémentation est de surcharger la fonction pour les conteneurs à accès aléatoire, qui autorisent d’écrire directement ''it+=N''. Ce type d’approche sort du cadre de cet article, le lecteur intéressé pourra rechercher "tag dispatching".
Ligne 78: Ligne 78:
 On arrive à l’une des critiques habituelles sur les templates C++ : les messages d’erreurs sont parfois un peu... cryptiques. On arrive à l’une des critiques habituelles sur les templates C++ : les messages d’erreurs sont parfois un peu... cryptiques.
  
-Un autre problème est qu’il n’y a pas de contrainte sur les arguments templates utilisés : alors que l’on a correctement définie des conditions d’utilisation de la fonction advance, il n’est pas possible d’écrire explicitement ces contraintes dans le code.+Un autre problème est qu’il n’y a pas de contrainte sur les arguments templates utilisés : alors que l’on a correctement défini des conditions d’utilisation de la fonction advance, il n’est pas possible d’écrire explicitement ces contraintes dans le code.
  
 ===== Les concepts en C++11 ===== ===== Les concepts en C++11 =====
Ligne 88: Ligne 88:
 > Les concepts sont un ensemble de contraintes que devra respecter les arguments templates. > Les concepts sont un ensemble de contraintes que devra respecter les arguments templates.
  
-En pratique, si l’on reprend l’exemple de la fonction advance, cela veut dire que l’on ne vas pas accepter n’importe quel type pour T, mais uniquement les types qui respectent la contrainte "est un itérateur valide, non constant et possédant l’opérateur ++".+En pratique, si l’on reprend l’exemple de la fonction advance, cela veut dire que l’on ne va pas accepter n’importe quel type pour T, mais uniquement les types qui respectent la contrainte "est un itérateur valide, non constant et possédant l’opérateur ++".
  
-Pour simplifier, les concepts sont donc un moyen de vérifier les conditions d’utilisation des classes et fonctions. Ces conditions sont en général définie dans la documentation, mais ne font pas partie de l’interface de la classe ou de la fonction. En cas d’erreur lors de l’utilisation de ces classes ou fonctions, l’erreur de compilation est générée à l’endroit du code qui nécessite le respect de ces contraintes et non directement au début de la classe ou fonction.+Pour simplifier, les concepts sont donc un moyen de vérifier les conditions d’utilisation des classes et fonctions. Ces conditions sont en général définies dans la documentation, mais ne font pas partie de l’interface de la classe ou de la fonction. En cas d’erreur lors de l’utilisation de ces classes ou fonctions, l’erreur de compilation est générée à l’endroit du code qui nécessite le respect de ces contraintes et non directement au début de la classe ou fonction.
  
-Un exemple d’utilisation des concepts est la bibliothèque standard (STL). Si l’on regarde la documentation de [[http://en.cppreference.com/w/cpp/iterator/advance|std::advance]] par exemple, on voit que la définition utilise deux paramètres template nommé ''InputIt'' et ''Distance'' et que ''InputIt'' doit respecter la contrainte des [[http://en.cppreference.com/w/cpp/concept/InputIterator|InputIterator]] (c’est-à-dire qu’il doit être un itérateur, être comparable avec l’égalité, être déréférençable, être incrémentale et d’autres conditions).+Un exemple d’utilisation des concepts est la bibliothèque standard (STL). Si l’on regarde la documentation de [[http://en.cppreference.com/w/cpp/iterator/advance|std::advance]] par exemple, on voit que la définition utilise deux paramètres template nommés ''InputIt'' et ''Distance'' et que ''InputIt'' doit respecter la contrainte des [[http://en.cppreference.com/w/cpp/concept/InputIterator|InputIterator]] (c’est-à-dire qu’il doit être un itérateur, être comparable avec l’égalité, être déréférençable, être incrémentable et d’autres conditions).
  
 ==== Ajouter une contrainte dans une fonction ==== ==== Ajouter une contrainte dans une fonction ====
Ligne 98: Ligne 98:
 Pour implémenter les concepts en C++11, vous allez utiliser deux nouvelles fonctionnalités du C++11 : les assertions statiques et les traits. Pour implémenter les concepts en C++11, vous allez utiliser deux nouvelles fonctionnalités du C++11 : les assertions statiques et les traits.
  
-Les assertions permettent de valider une expression et déclenche une erreur lorsque cette expression est fausse. Il est existe sous deux formes : les assertions à la compilation (en C++11 ou avec Boost) et à l’exécution. La première forme utilise la fonction ''static_assert'' et prend deux paramètres : une expression constante retournant un booléen et un message d’erreur à afficher :+Les assertions permettent de valider une expression et déclenchent une erreur lorsque cette expression est fausse. Elles existent sous deux formes : les assertions à la compilation (en C++11 ou avec Boost) et à l’exécution. La première forme utilise la fonction ''static_assert'' et prend deux paramètres : une expression constante retournant un booléen et un message d’erreur à afficher :
  
 <code cpp> <code cpp>
Ligne 139: Ligne 139:
 </code> </code>
  
-Dans ce cas, la contrainte "est un pointeur" est vérifié dès la compilation et retourne un message d’erreur spécifique et compréhensible. Si on appelle cette fonction avec un type non valide (par exemple une référence), on obtient le message d’erreur suivant :+Dans ce cas, la contrainte "est un pointeur" est vérifiée dès la compilation et retourne un message d’erreur spécifique et compréhensible. Si on appelle cette fonction avec un type non valide (par exemple une référence), on obtient le message d’erreur suivant :
  
 <code> <code>
Ligne 154: Ligne 154:
 On voit qu’il subsiste encore des problèmes de lisibilité des messages d’erreur : le message ajouté dans l’assertion est mélangé parmi du bruit, ce qui complique un peu la lecture. On voit qu’il subsiste encore des problèmes de lisibilité des messages d’erreur : le message ajouté dans l’assertion est mélangé parmi du bruit, ce qui complique un peu la lecture.
  
-Remarque : avec cette implémentation des concepts, ceux-ci n’apparaissent pas explicitement dans l’interface de la fonction, ce qui réduit leur intérêt. Les concepts de la prochaine norme du C++ sera directement définies dans l’interface des classes et fonctions, voir la dernière partie de ce tutoriel.+Remarque : avec cette implémentation des concepts, ceux-ci n’apparaissent pas explicitement dans l’interface de la fonction, ce qui réduit leur intérêt. Les concepts de la prochaine norme du C++ seront directement définis dans l’interface des classes et fonctions, voir la dernière partie de ce tutoriel.
  
 ==== Ajouter plusieurs contraintes à une fonction ==== ==== Ajouter plusieurs contraintes à une fonction ====
  
-Dans le cas général, vous n’aurez pas une seule contrainte à vérifier, mais plusieurs. Vous avez deux solutions pour tester ces contraintes : soit vous regrouper plusieurs expressions avec l’opérateur AND dans une seule insertion, soit vous écrivez plusieurs assertions. La seconde version permet d’avoir un message d’erreur spécifique pour chaque expression, la première est plus concise.+Dans le cas général, vous n’aurez pas une seule contrainte à vérifier, mais plusieurs. Vous avez deux solutions pour tester ces contraintes : soit vous regroupez plusieurs expressions avec l’opérateur AND dans une seule insertion, soit vous écrivez plusieurs assertions. La seconde version permet d’avoir un message d’erreur spécifique pour chaque expression, la première est plus concise.
  
 Si l’on reprend le code d’exemple sur la fonction advance, on peut ajouter la contrainte "n’est pas constant" avec la première forme : Si l’on reprend le code d’exemple sur la fonction advance, on peut ajouter la contrainte "n’est pas constant" avec la première forme :
Ligne 185: Ligne 185:
 ==== Hiérarchie de concepts et regroupement des contraintes ==== ==== Hiérarchie de concepts et regroupement des contraintes ====
  
-On voit qu’un type doit finalement respecter un nombre important de contraintes et il va être lourd d’exprimer explicitement dans chaque fonction chacune de ces contraintes. Pour éviter cela, on va créer des hiérarchies de concepts, chaque concept étant définie à partir d’autres concepts et des contraintes propres.+On voit qu’un type doit finalement respecter un nombre important de contraintes et il va être lourd d’exprimer explicitement dans chaque fonction chacune de ces contraintes. Pour éviter cela, on va créer des hiérarchies de concepts, chaque concept étant défini à partir d’autres concepts et des contraintes propres.
  
 Si on regarde par exemple les [[http://en.cppreference.com/w/cpp/iterator|itérateurs de la biblitohèque standard]], on voit qu’il existe une hiérarchie dans les concepts : Si on regarde par exemple les [[http://en.cppreference.com/w/cpp/iterator|itérateurs de la biblitohèque standard]], on voit qu’il existe une hiérarchie dans les concepts :
Ligne 196: Ligne 196:
 On retrouve ce type de hiérarchie dans d’autres modules de la bibliothèque standard, comme par exemple les [[http://en.cppreference.com/w/cpp/io|entrées-sorties]]. On retrouve ce type de hiérarchie dans d’autres modules de la bibliothèque standard, comme par exemple les [[http://en.cppreference.com/w/cpp/io|entrées-sorties]].
  
-Ainsi, nous allons modifier le code d’exemple précédent pour refactoriser : au lieu d’exprimer chaque contraintes directement dans la fonction ''advance'', nous allons créer un trait ''is_iterator'', regroupant les contraintes appliquées sur les itérateurs.+Ainsi, nous allons modifier le code d’exemple précédent pour refactoriser : au lieu d’exprimer chaque contrainte directement dans la fonction ''advance'', nous allons créer un trait ''is_iterator'', regroupant les contraintes appliquées sur les itérateurs.
  
-Pour cela, nous allons simplement créer une nouvelle structure ''is_iterator'' qui contient une seule variable membre (constante et statique) value. La valeur de cette variable est initialisée à partir des expressions booléennes permettant de vérifier que le type est un pointeur et n’est pas constant. La code suivant présente un exemple d’implémentation :+Pour cela, nous allons simplement créer une nouvelle structure ''is_iterator'' qui contient une seule variable membre (constante et statique) value. La valeur de cette variable est initialisée à partir des expressions booléennes permettant de vérifier que le type est un pointeur et n’est pas constant. Le code suivant présente un exemple d’implémentation :
  
 <code cpp> <code cpp>
Ligne 223: Ligne 223:
 Remarque 1 : le concept d’itérateur est plus complexe que simplement vérifier que le type est un pointeur et n’est pas constant. Dans le cadre de cet article, nous n’entrerons pas plus dans les détails. Remarque 1 : le concept d’itérateur est plus complexe que simplement vérifier que le type est un pointeur et n’est pas constant. Dans le cadre de cet article, nous n’entrerons pas plus dans les détails.
  
-Remarque 2 : la création de ces traits supplémentaires peut paraître lourd, surtout si vous les utiliser qu’à un seul endroit dans votre code. Cela nécessitera de créer des fichiers supplémentaires, qui ne seront utilisés qu’une seule fois. Cependant, en termes de conception, cette approche respecter le [[http://blog.emmanueldeloget.com/index.php?post/2006/09/21/15-le-principe-ouvert-ferme|principe ouvert-fermé]] (chaque trait étant minimaliste et spécialisé, il ne sera pas nécessaire de la modifier, sauf erreur de conception ou d’implémentation) et renforce donc la réutilisation du code et sa robustesse. N’hésitez pas à vous créer votre propre bibliothèque de traits réutilisable (ce sont des templates, donc facilement déployable – il faut juste inclure les fichiers – et n’ont pas de coût à l’exécution) ou à utiliser [[http://www.boost.org/doc/libs/1_54_0/libs/type_traits/doc/html/index.html|Boost.TypeTrait]].+Remarque 2 : la création de ces traits supplémentaires peut paraître lourd, surtout si ne vous les utilisez qu’à un seul endroit dans votre code. Cela nécessitera de créer des fichiers supplémentaires, qui ne seront utilisés qu’une seule fois. Cependant, en termes de conception, cette approche respecte le [[http://blog.emmanueldeloget.com/index.php?post/2006/09/21/15-le-principe-ouvert-ferme|principe ouvert-fermé]] (chaque trait étant minimaliste et spécialisé, il ne sera pas nécessaire de le modifier, sauf erreur de conception ou d’implémentation) et renforce donc la réutilisation du code et sa robustesse. N’hésitez pas à vous créer votre propre bibliothèque de traits réutilisables (ce sont des templates, donc facilement déployables – il faut juste inclure les fichiers – et n’ont pas de coût à l’exécution) ou à utiliser [[http://www.boost.org/doc/libs/1_54_0/libs/type_traits/doc/html/index.html|Boost.TypeTrait]].
  
 ===== Les concepts en C++14 ===== ===== Les concepts en C++14 =====
  
-Les concepts devaient initialement être ajouté dans le C++14, mais ils seront finalement disponibles sous forme d'un //Technical Specification// dans le draft [[http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3929.pdf|Concepts Lite N3929]]. Nous allons voir rapidement dans cette partie la syntaxe proposée par la prochaine norme.+Les concepts devaient initialement être ajoutés dans le C++14, mais ils seront finalement disponibles sous forme d'un //Technical Specification// dans le draft [[http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3929.pdf|Concepts Lite N3929]]. Nous allons voir rapidement dans cette partie la syntaxe proposée par la prochaine norme.
  
 Remarque : les codes d’exemples présentés dans cette partie sont directement extraits du draft. Remarque : les codes d’exemples présentés dans cette partie sont directement extraits du draft.
  
-Le draft propose deux écritures pour utiliser les concepts. Dans la première, la contrainte est exprimée directement dans les paramètre template, en remplaçant le nom-clé class ou typename par le concept que doit respecter le paramètre template.+Le draft propose deux écritures pour utiliser les concepts. Dans la première, la contrainte est exprimée directement dans les paramètres templates, en remplaçant le nom-clé ''class'' ou ''typename'' par le concept que doit respecter le paramètre template.
  
 <code cpp> <code cpp>
Ligne 290: Ligne 290:
 ===== Conclusion et remerciements ===== ===== Conclusion et remerciements =====
  
-On voit au final que l’implémentation de la vérification des concepts (qui représentent qu’une partie de l’utilité des concepts) est relativement simple et légère en C++11. J’espère avoir réussi à vous montrer dans cet article l’intérêt à les utiliser, même pour les débutants.+On voit au final que l’implémentation de la vérification des concepts (qui ne représentent qu’une partie de l’utilité des concepts) est relativement simple et légère en C++11. J’espère avoir réussi à vous montrer dans cet article l’intérêt à les utiliser, même pour les débutants.
  
-Merci bientôt à winjérôme…+Merci à Winjerome…
genericite_concepts_et_c_14.1395216889.txt.gz · Dernière modification: 2014/03/19 09:14 par gbdivers