Almost Always use Auto

Quoi ?! Mais il nous dit d'abuser d'un mot clé créé pour les flemmards! Moi je suis un vrai développeur! Alors déjà calmez vous. Oui c'est un mot clés qui raccourci parfois. Mais ce n'est pas ça qui le rend si intéressant. Je vais vous l'expliquer point par point. Mais avant tout définissont auto. Auto est un mot clé qui permet de faire de la template argument deduction (j'utiliserai les versions anglaises des mots techniques plutôt que leur équivalent français parce que déjà, c'est plus joli, et en plus, c'est moi qui décide). Bon du coup vous n'êtes pas beaucoup plus avancés. Vous devez sûrement vous demander : C'est quoi un template ? C'est quoi de la template argument deduction ?


  1. C'est quoi un template ?

    Et bien définissons un template : - Il s'agit d'une entité. Ahah. Vous aviez cru qu'on avait avait fini avec les définitions récursives. Bah non. Bon en vrai cette fois si c'est simple, il ne s'agit que d'une liste de chose qui existe en C++ : int i = 42; // values std::string s; // objects int a[2] = {1, 2}; auto [x, y] = a; // structured bindings void func(){} // functions enum class COLOR { RED, GREEN, BLUE }; // enums class C { int _m; // class members }; template <class T> void func2(){}; // templates template <int> void func2(){}; // templates specializations template <class ...ARGS> void f(ARGS...) {} // parameter packs namespace n{} // namespaces Vous n'avez pas besoin de tous les comprendre. L'important c'est que vous puissiez voir. Du coup c'est bien joli de savoir ce qu'est un template mais ça sert à quoi ? Ca sert à définir : template <class T> // class templates class C {}; template <class T> // function templates void func(){} template <class T> // alias templates using alias = C<T>; template <class T> // variable templates constexpr T pi = T(3.1415926535897932385); template <class T, class U> // concepts (C++20) concept Derived = std::is_base_of<U, T>::value;
  2. C'est quoi de la template argument deduction ?

    C'est le concept qui permet de faire de la programmation générique. En fait, votre compilateur va remplir les trous laisser par les templates en fonction des cas. Par exemples quand vous utilisez un std::vector, vous devez lui passer un type pour remplir son template. Dans ce cas là vous forcer le compilateur à utiliser votre type pour remplir les trous. Et puisque quelques exemples valent plus que beaucou de parole : template <class T> // Fonctions génériques T func(T){} template <class E> // Spécialization d'interfaces class EntityContainer : public IEntityContainer { ... }; template <class C> // Classes génériques class Class { C getValue() const; }; template <class ...TYPES> // Variadic templates void f(TYPES... values); std::vector<MyClass> v; // Votre std::vector va contenir des éléments du type MyClass Sauf que depuis tout à l'heure on ne voit toujours pas auto.
  3. C'est quoi le rapport avec auto ?

    Auto va vous permettre de déduire le type au moment de vos déclarations. Cela va, par exemple, permettre de déterminer automatique le type d'un iterator; comme dans vector.begin(); Faisons un petit jeu. Essayez de trouver le type des variables suivantes déclarées avec auto (la réponse est donnée plus bas). Un petit indice : la difficulté est croissante. int val = 0; auto a = val; auto &b = val; const auto c = val; const auto &d = val; int &ir = val; auto e = ir; int *ip = &val; auto f = ip; const int ci = val; auto g = ci; const int &cir = val; auto h = cir; const int *cip = &val; auto i = cip; int * const ipc = &val; auto j = ipc; const int * const cipc = &val; auto k = cipc; int val2 = 0; auto l{ val }; auto m = { val }; auto prend par défaut le type de la variable depuis laquelle il reçoit sa valeur. Cependant, dans les cas où la variable est constante, elle perd cette attribut. En fait on perd les const/volatile et &/&& du plus haut niveau. Plus surprenant, dans les derniers cas, au lieu de créer un int, auto choisi une liste d'initialization. C'est du à la hierarchie des types que auto déduis. Pourtant dans certains cas avec les nouvelles versions du C++, auto déduira directement le type plutôt qu'une liste s'il le juge plus efficace. int val = 0; auto a = val; // int auto &b = val; // int & const auto c = val; // const int const auto &d = val; // const int& int &ir = val; auto e = ir; // int int *ip = &val; auto f = ip; // int * const int ci = val; auto g = ci; // int const int &cir = val; auto h = cir; // int const int *cip = &val; auto i = cip; // const int * int * const ipc = &val; auto j = ipc; // int * const int * const cipc = &val; auto k = cipc; // const int * int val2 = 0; auto l{ val }; // std::initializer_list<int> auto m = { val }; // std::initializer_list<int>
  4. Pourquoi le AAA (Almost Always use Auto) ?

    Je vais organiser cette partie en différentes sections reprenant des choses que j'ai entendu ou dit, par le passé sur auto.

    La paresse... ou la performance ?

    Je commence directement par le plus évident. "Auto c'est pour ceux qui veulent écrire moins" Comme je l'ai montré dans l'exercice précédent, auto prend parfois un type qui n'est pas celui que nous aurions choisis car il peut augmenter la performance. Pour vous donner un second exemple qui vous parlera peut-être plus. C'est une erreur que j'ai souvent faite puis souvent vu. Quand vous souhaitez créer une lambda et la stocker, vous pourriez vous dire "tiens je vais la mettre dans une std::function ça à l'air pas mal" Sauf qu'en réalité std::function est une classe inutilement lourde pour ce cas ci. Si vous utilisez auto, il créera à la compilation un type propre à votre lambda et bien plus léger. En testant sur compiler explorer (que je vous recommande), avec l'optimisation au maximum on peut réduire un code composé d'un main et de l'assignation d'une lambda de 24 à 3 lignes ! Mais j'avoue tout de même qu'il est bien plus agréable, à lire comme à écrire: for (std::vector<int>::iterator &it = vector.begin(); it != vector.end(); ++it); // vs for (auto &it : vector);

    La perte d'engagement... ou la sécurité ?

    Certains repprocheront également à auto de perdre en engagement. Il est vrai que vous souhaitez parfois créer un type précis. Mais auto ne vous en empêche pas! Même plus, il va vous empêcher de créer votre variable sans l'initialiser. auto i = int{ 0 }; // Déclare et initialise un int i de valeur 0 auto i; // Erreur. Impossible de déduire le type de i int i = 0; // Déclare et initialise un int i de valeur 0 int i; // Pas d'erreur et pourtant cela risque de causer un bug

    Perte de lisibilité... ou maintenabilité ?

    En fait auto, en rendant votre code générique, va vous permettre plus de modifications sans risque. Par exemple, une fonction qui calcul la taille de chaîne de caractère en c-style (pointeur). Vous l'écrivez vous renvoyant un int. Mais un jour vous décrivez que cette taille peut aller au delà d'un int et qu'il existe un type, size_t, qui convient mieux. Vous changez le type de retour de votre fonction. Et la patatra, plus rien de compile (parce que vous êtes quelqu'un de bien vous avez pensez à mettre les flags qui empêchent les convertions implicites). Vous devez alors changer toutes les variables qui récupèrent le retour de cette fonction. Si vous aviez utilisé, votre compilateur l'aurait fait pour vous. Il y a aussi le fait qu'en réalité on ignore souvent le type des variables qu'on utilise. Par exemple donnez moi le nombre de type connus dans ce code : template <class Container, class Value> void append_unique(Container &c, const Value &v) { if (find(begin(c), end(c), v) == end) c.emplace_back(v); assert(!c.empty()); } Ne vous en faites pas j'attend. Allez-y, chercher. Bon alors, combien ? Eh bien 0. On sait que Container doit être un type avec une function emplace_back et qui renvoie un début et une fin sur l'appel de begin(c) et end(c)). On sait que Value doit être une valeur qui rentre dans Container. On sait que l'on test si le container est vide avec empty(). Et c'est tout ce que l'on a besoin de savoir. Est-ce que l'on veut savoir si find renvoie un booléen ? Un iterateur ? Non, nous souhaitons juste remplir notre container avec une seule itération de notre valeur.

Conclusion

J'éspère vraiment que je vous aurais convaincu. Et à défaut de vous avoir donné un regard différent sur auto. Personnellement, j'aimerais remercier Herb Sutter (dont je me suis beaucoup inspiré pour ce tutoriel) et Jason Turner (qui fait un travail incroyable sur les secrets du C++). Ce sont deux monstres du C++, donc vous en aurez peut-être déjà entendu parlé. Dans le cas contraire allez voir leur travail: Herb Sutter et Jason Turner