Les lambdas & opérateurs
Le C++ permet de redéfinir des opérateurs comme n'importe quelle fonction : il suffit de respecter le nom de l'opérateur et d'avoir une signature qui lui correspond.
De nombreuses interfaces implicites utilisées par les fonctions template se basent sur ces opérateurs. par exemple, les itérateurs utilisent au minimum les opérateurs suivants :
Déréférencer l'itérateur avec
T& operator*()
.Accéder aux membres de la valeur de l'itérateur avec
T* operator->()
.Incrémenter l'itérateur avec
T& operator++()
.
Function objects & std::function
Les function objects sont les objets pouvant être appelés comme des fonctions. Il s'agit de pointeurs de fonction ou d'objets implémentant l'opérateur ()
. L'opérateur peut être défini plusieurs fois tant que la résolution des appels n'est pas ambigu.
Parfois, ces objets sont appelés functors en C++, ce qui n'a rien à voir avec les functors que l'on retrouve en programmation fonctionnelle.
std::function<Ret(Args...)>
permet de stocker n'importe quel function object pouvant être copié, et dont les arguments et la valeur de retour correspondent avec la signature de la classe :
L'appel à std::function
est cependant moins performant qu'un appel direct au function object, car il nécessite une indirection qui empêche souvent l'inline, et déclenche une allocation dynamique selon la taille du function object.
Le concept de Callable permet à (entre autres) std::function
ou std::thread
d'utiliser l'objet comme une fonction : cela comprend les function objects ainsi que quelques cas supplémentaires, comme les pointeurs de fonction membres. Les Callables sont utilisés avec std:invoke(f, args...)
.
Lambdas
Parfois appelées closures, les lambdas sont des function objects créées automatiquement par le compilateur. Elles peuvent stocker des variables ou des références, qui sont spécifiées avec la capture donnée par les crochets :
Les captures peuvent également créer de nouvelles variables, et spécifier un mode de capture par défaut : dans ce cas, toutes les variables locales utilisées dans la capture seront capturées par valeur ou par référence. Attention cependant à ne pas avoir de dangling references (références désignant un objet n'existant plus) si la lambda vit plus longtemps que les objets capturés par références.
Si la lambda n'a pas de capture, elle peut être implicitement cast en pointeur de fonction. Elle peut également prendre des arguments template si ses arguments sont de type auto
, et être constexpr
.
Quelques opérateurs
Voici une liste des opérateurs disponibles, dont ceux qui peuvent être redéfinis.
Lorsque des objets sont créées et détruits avec new
et delete
, les opérateurs operator::new
et operator::delete
sont appelés. Ces opérateurs ne s'occupent pas de construire ou détruire l'objet, mais uniquement d'allouer et de désallouer sa mémoire.
Ils peuvent également être définis pour les tableaux, avec operator::new[]
et operator::delete[]
. Ces opérateurs peut être définis de façon globale ou uniquement pour certaines classes :
Les opérateurs de comparaison (==, !=, >, <=, ...
) peuvent être redéfinis. L'opérateur <
est utilisé par std::less
pour comparer deux objets : il s'agit d'un function object, utilisé par défaut par std::map
pour trier ses clés.
Les opérateurs arithmétiques (*, /, -=, ++, ...
) et les opérations sur les bits (<<, &, |=, ~, ...
) peuvent être redéfinies. <<
et >>
sont également utilisés comme opérateurs de flux (pour envoyer ou recevoir des données), comme avec std::iostream
.
Les opérateurs litéraux peuvent s'appliquer sur des chaînes de caractères ou des nombres litéraux. Ils sont souvent utilisés pour avoir une meilleure syntaxe. Les noms des opérateurs litéraux définis doivent commencer avec un underscore (les autres noms sont réservés).
Pour aller plus loin
L'opérateur & peut être redéfini pour changer la valeur obtenue lorsque l'on prend l'adresse d'un objet. std::addressof
permet de récupérer l'adresse de l'objet dans tous les cas.
L'opérateur new
peut accepter des paramètres supplémentaires, qui seront passés avec la syntaxe d'un placement new.
Boost a une librairie header-only récente, CallableTraits, qui permet de détecter les arguments et la valeur de retour de function objects.
Challenge
(**) Créer une fonction qui prend un nombre arbitraire de function objects en paramètres, et renvoie un function object qui met à disposition tous les operateurs ()
des paramètres. Exemple :
Last updated