Le type std::optional

Depuis C++ 17, un type std::optional est offert pour représenter le fait d'avoir peut-être quelque chose. Un optional<T> modélise donc un possible T. Par exemple :

#include <optional>
#include <iostream>
using namespace std;
optional<int> division_entiere(int num, int denom) {
   if (denom == 0) return {}; // optional vide
   return { num / denom };   // optional non vide
}
int main() {
   if (int num, denom; cin >> num >> denom)
      if (auto quotient = division_entiere(num, denom); quotient) // si on a un résultat...
         cout << quotient.value() << endl;                        // ... alors on l'affiche
}

Un tel type est utile à plusieurs égards, que ce soit pour signaler l'absence d'un résultat; pour signaler une erreur (sans toutefois en donner la nature) sans avoir recours à une levée d'exception; pour modéliser des paramètres optionnels justement; etc.

 Les fonctionnalités cles de ce type sont :

Fonctionnalité Rôle Exemple

Vider un optional<T> ou créer un optional<T> vide

Un optional<T> sera vide par défaut, si on lui affecte nullopt ou si on appelle sa méthode reset()

optional<int> a;
optional<int> b = nullopt;
// ...
b = 3;
// ...
b = nullopt; // ou b = {};
a = 4;
// ...
a.reset();

Insérer une valeur

Il est possible d'insérer une valeur dans un optional<T> :

  • À la construction
  • Par une affectation
  • Par emplacement
optional<int> a{ 3 };
optional<int> b;
// ...
b = 3;
auto p = "J'aime mon prof";
optional<string> c;
c.emplace(begin(p), end(p));

Tester pour la présence d'une valeur

Il est possible de tester un optional<T> pour la présence d'une valeur :

  • Un optional<T> se convertit en booléen true à l'aide de la méthode operator bool() seulement s'il n'est pas vide, autrement il se convertit en booléen false
  • Il est aussi possible d'appeler sa méthode has_value() pour obtenir le même effet
optional<int> f();
// ...
if(auto opt = f(); opt)
   cout << opt.value() << endl;
// ...
if(auto opt = f(); opt.has_value())
   cout << opt.value() << endl;

Obtention de la valeur entreposée

Il est possible d'extraire le T d'un optional<T> de plusieurs manières :

  • Utiliser sa méthode value(). Dans ce cas, si le optional<T> est vide, alors une exception de type bad_optional_access sera levée
  • Utiliser son opérateur * ou son opérateur ->, un peu à la manière d'un pointeur. Dans ce cas, Dans ce cas, si le optional<T> est vide, alors on tombe dans un vilain cas de comportement indéfini
  • Offrir une alternative avec sa méthode value_or(alt). Dans ce cas, si le optional<T> est vide, alors alt sera utilisé à la place
optional<string> f();
// ...
if(auto opt = f(); opt)
   cout << opt.value().size() << ' '
        << (*opt).size() << ' '
        << opt->size() << ' '
        << opt.value_or("zut") << endl;

Comparaisons

Un optional<T> est comparable à l'aide des opérateurs relationnels et se comporte comme un T, à ceci près qu'un optional<T> vide est plus petit que tout optional<T> non-vide. Deux optional<T> vide sont égaux au sens de ==

optional<int> a{ 2 }, b{ 3 };
assert(a < b);
assert(optional<int>{} < a);
assert(optional<int>{} == optional<int>{});

C'est un chouette type, qui a un mandat clair et qui l'accomplit bien. Notez qu'à moins que votre biblothèque standard ne fasse des acrobaties suspectes avec certains types T, sizeof(<optional<T>>)>sizeof(T). Notez aussi que copier un optional<T> peut être coûteux si copier T est coûteux.

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !