Les nouveaux standards à partir C++ 11 apportent énormément de fonctionnalités, que ça soit dans le langage lui-même ou bien dans la bibliothèque standard. A tel point que cela peut être compliqué de suivre toutes les nouveautés, c’est pourquoi j’écris cet article, afin de faciliter cet apprentissage étape par étape.
C++11 feels like a new language: The pieces just fit together better than they used to and I find a higher-level style of programming more natural than before and as efficient as ever.(Bjarne Stroustrup)
Je vais donc vous parler des mots clefs auto et decltype, apparus en C++11, et vous montrer leurs significations, ce à quoi ils servent, et comment bien les utiliser. Auto existait avant C++ 11 mais sa signification a été modifiée car il n’était pas utilisé car inutile.
Auto : déduire le type de variables
Commençons par le mot clef auto : c’est celui des deux avec le plus de champs d’application, et par conséquent celui que vous verrez et utiliserez probablement le plus. Auto permet tout d’abord de faire de la déduction de type. Ici, avec le premier exemple, le compilateur déduit tout seul que i est de type int parce que 5 est un entier, j est un int car l’addition d’un int avec un int renvoie un int, et str est une std::string parce que function_returning_a_string() retourne une std::string.
std::string function_returning_a_string(); void auto_simple_example() { auto i = 5; // int i; auto j = i + 10; // int j; auto str = function_returning_a_string(); // std::string str }
A noter que la déduction a lieu à la compilation ! Au niveau de l’exécutable généré il n’y a aucune différence entre « int i = 5 » et « auto i = 5»
Auto : type de retour de fonction
L’autre utilité du mot clef auto est de pouvoir l’écrire à la place du type de retour d’une fonction, pour le renseigner après les arguments, avec une flèche :
auto function_with_trailing_return() -> int { return 5; }
Petite amélioration qui arrive en C++ 14, le type peut être déduit automatiquement comme ceci :
// Works only with C++14 auto function_with_deduced_returned_type() { return 5; // Return an int }
Decltype : détection de types simples
Maintenant, passons au mot clef decltype : il permet d’avoir le type d’une variable, d’une fonction ou bien d’une expression. Dans l’exemple de droite, j’ai un entier i : si je souhaite déclarer une variable j du même type que i, je peux utiliser decltype(i) pour renseigner le type
de j. Il me permet également, par exemple, de déclarer une variable h qui soit du type du résultat de «i + j».
int i = 1; decltype(i) j = i; // int j decltype(i + j) h = i + j; // int h
Sur des entiers, l’utilisation n’a pas l’air très utile, mais on aurait pu imaginer vouloir connaitre le type de retour d’une addition entre une std::string et un const char*, qui renvoie une std::string.
const char* c_string = "C++ is cool." std::string std_str = "C++11 is even better."; decltype(std_str + c_string) result = std_str + c_string;
Decltype : détection de types complexes
Vous avez sûrement encore du mal à voir l’utilité de ce mot-clef pour l’instant. Prenons un exemple que ce mot-clef permet de simplifier. Voyez la fonction ci-dessous : elle prend en paramètre une callback, fait des calculs, puis appelle la callback prise en paramètre et retourne la valeur renvoyée par la callback, le tout sans connaître le type de retour de la callback prise en paramètre ! Avec decltype, on peut connaître facilement ce que retourne la «cb() ».
template <typename Callback> auto more_advanced_example(Callback cb) -> decltype(cb()) { // Do some calculation return cb(); }
Dans l’exemple, j’ai utilisé le mot-clef auto pour pouvoir mettre le type de retour à droite, car si je l’avais mis à gauche, je n’aurais pas pu utiliser l’argument « cb » car il n’aurait pas été encore déclaré. Certains se demanderont peut-être comment faire si la callback était censée prendre des arguments, et bien c’est presque pareil :
template <typename Callback> auto more_advanced_example(Callback cb, int i, int j) -> decltype(cb(i,j)) { // Do some calculation return cb(i, j); }
Decltype : son fidèle compagnon std::declval
Autre problématique qui peut arriver : il est possible que vous n’ayez pas de variable du bon type sous la main pour pouvoir créer des expressions dont le type serait déduit. Par exemple, si je voulais ne pas mettre mon type de retour à droite après la flèche, cette notation ne marcherait pas car ni cb, ni i, ni j ne sont déclarés à ce moment-là. Pour pallier à ce problème, il y a la fonction std::declval : elle permet d’obtenir une variable de n’importe quel type pour être utilisée dans des expressions de déduction de type.
template <typename Callback> decltype(std::declval<Callback>()()) declval_example(Callback cb) { return cb(); }
Ne jamais utiliser std::declval pour autre chose que déduire un autre type avec decltype. En C++14, on aurait pu aussi utiliser le mot clef auto pour déduire tout seul le type de retour de la fonction comme ceci :
template <typename Callback> auto auto_is_powerfull(Callback cb, int i, int j) { return cb(i, j); }
Différence entre détection et déduction de type
Revenons un moment sur mot-clef auto. Comme je l’ai dit précédemment, auto déduit le type alors que decltype permet de détecter le type. Voyons donc un exemple où la différence se fait voir :
std::vector<std::string> strings {"auto", "and", "decltype"}; auto str = strings[0]; // std::string str decltype(strings[0]) str_ref = strings[0]; // std::string& str_ref;
L’opérateur [] d’un std::vector renvoie une référence. Pourtant, avec auto on obtient une std: :string et non une std::string&, alors qu’avec decltype, on obtient bien une std ::string&. La ligne avec decltype semble bien verbeuse, pour pallier à ça, on peut soit :
1 auto& str_ref = strings[0];
1 decltype(auto) str_ref = strings[0];
Il reste une dernière possibilité du mot clef auto pour déduire tout seul les arguments d’une fonction lambda, mais j’en parlerai ultérieurement, dans un article dédié aux lambdas.
Quelques domaines d’application
Maintenant, voyons quelques-uns des champs d’application les plus courants pour ces mots-clefs.
Les types longs peu importants
Les types longs à écrire dont le type est assez explicite même sans le marquer, comme des itérateurs retournés par les fonction « begin » / « cbegin »:
std::vector<int> ints {0, 1, 2, 3, 4}; for (std::vector<int>::const_iterator it = ints.cbegin(); it != ints.cend(); ++it) ; for (auto it = ints.cbegin(); it != ints.cend(); ++it) ;
Je ne sais pas vous, mais moi je trouve que la seconde ligne est bien plus lisible.
Les types évidents
Quand le type n’est pas important dans l’expression, ou bien qu’il est évident :
auto int_ptr = std::make_unique<int>(5); // std::unique_ptr<int> int_ptr auto my_pair = std::make_pair(5, 10); // std::pair<int, int> my_pair
Stocker une lambda
Sachant qu’il est impossible de connaitre le vrai type d’une lambda (car généré par le compilateur) , pour le stocker, soit on peut utiliser auto, soit on peut le stocker dans des wrappers tels que std::function (ce qui a un léger coût par contre).
Le code générique/utilisant des templates
Dans du code totalement générique, comme par exemple les fonctions prenant en paramètres des callbacks comme présentées plus haut dans l’article, puisque l’on veut des éléments qui peuvent prendre une multitude de types ayant les bonnes propriétés, auto et decltype sont des vrais atouts.
La métaprogrammation
En métaprogrammation, qui se base énormément sur le fait de jouer avec les types et leurs propriétés, decltype est un outil presque indispensable (mais la métaprogrammation reste, il est vrai, un domaine de niche en C++).
Les abus à éviter
Attention à ne pas abuser du mot clef auto ! Si on le met partout, le code peut vite devenir difficile à lire, cela doit donc rester du sucre syntaxique et, comme le sucre dans la vraie vie, c’est à prendre avec modération.
Attention également à ne pas utiliser decltype là où c’est inutile, cela risquerait d’ajouter de la verbosité sans apporter la flexibilité que ce mot-clef peut apporter dans certains cas.
Les mots-clefs auto et decltype peuvent, dans certains cas, vous simplifier la vie et vous permettre d’écrire du code plus clair et plus concis, à condition de ne pas en abuser.
Ils vous permettent aussi d’écrire du code très générique en se liant très bien aux templates. De plus, ils se marient très bien avec les nouvelles fonctionnalités arrivées en C++11 telles que les lambdas.
by Lena Bertho
Si vous souhaitez découvrir nos opportunités professionnelles, c'est par ici : https://www.elephant-technologies.fr/carriere/opportunites