C++11, les mots clefs « auto » et « decltype »
Tags: cpp, french
Published: 2022-01-27 11:26:57

---
layout: post
title: C++11, les mots clefs « auto » et « decltype »
published: true
description: Avec C++11 arrivent les nouveaux mots-clefs "auto" et "decltype", je vais vous expliquer à quoi ils servent, comment les utiliser.
tags: [Cpp, French]
---
## Article::Article
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)](https://www.stroustrup.com/C++11FAQ.html)

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**.

```C++
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 :

```C++
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 :

```C++
// 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**».

```C++
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](https://en.cppreference.com/w/cpp/string/basic_string/operator%2B).

```C++
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()** ».

```C++
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 :

```C++
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](https://en.cppreference.com/w/cpp/utility/declval) : elle permet d’obtenir une variable de n’importe quel type pour être utilisée dans des expressions de déduction de type.

```C++
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 :

```C++
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 :
```C++
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](https://fr.cppreference.com/w/cpp/container/vector/operator_at). 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 :
* Ajouter soi-même la référence avec le mot clef **auto**, comme ceci :
```C++
auto& str_ref = strings[0];
```
* Ou utiliser – en C++14 - la syntaxe « **decltype(auto)** », comme ceci :
```C++
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](https://en.cppreference.com/w/cpp/language/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 »**:

```C++
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 :

```C++
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](https://fr.wikipedia.org/wiki/M%C3%A9taprogrammation), 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](https://fr.wikipedia.org/wiki/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.

## Article::~Article
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.