Template-metaprograming ou constexpr, introduction et comparaison en C++17, partie 2

Mar 11 2023
En C++, SFINAE (l'échec de la substitution n'est pas une erreur) et constexpr sont deux concepts importants qui jouent respectivement un rôle clé dans la métaprogrammation des modèles et l'évaluation au moment de la compilation. Ils permettent tous deux de créer un code plus puissant et efficace, mais ils ont des cas d'utilisation et des limites différents.

En C++, SFINAE (l'échec de la substitution n'est pas une erreur) et constexpr sont deux concepts importants qui jouent respectivement un rôle clé dans la métaprogrammation des modèles et l'évaluation au moment de la compilation. Ils permettent tous deux de créer un code plus puissant et efficace, mais ils ont des cas d'utilisation et des limites différents. Dans cet article, nous expliquerons ces concepts en profondeur, fournirons des exemples de code et comparerons leurs forces et leurs faiblesses.

SFINAE

SFINAE est un mécanisme qui permet à un compilateur d'exclure des fonctions de l'ensemble de surcharge selon qu'une certaine expression peut ou non être évaluée. Par exemple, nous pouvons utiliser SFINAE pour créer une fonction modèle qui calcule la factorielle d'un nombre, mais uniquement pour les types intégraux :

Dans ce code, nous utilisons std::enable_ifpour activer ou désactiver conditionnellement le modèle de fonction selon qu'il Ts'agit d'un type intégral. Si Tn'est pas entier, std::enable_ifcela entraînera un échec de substitution et le compilateur supprimera cette fonction de l'ensemble de surcharge. Cela empêchera toute erreur de se produire lors de la compilation, car la fonction ne sera pas instanciée.

Nous pouvons également utiliser SFINAE pour créer des fonctions de modèle plus complexes qui dépendent de plusieurs conditions. Par exemple, nous pouvons créer une fonction modèle qui n'accepte que les types qui ont une ou plusieurs fonctions membres spécifiques. Avant de faire cela, nous avons juste besoin de comprendre l' utilitaire std::declval . Il permet de créer des objets temporaires d'un certain type sans constructeur par défaut. Exemple de cppreference.com que j'ai collé dans l' explorateur du compilateur :

Comme ci-dessus, vous pouvez voir que la structure NonDefaulta le destructeur par défaut supprimé ! Nous voulons comparer que les types de ce dernier et le Defaultretour de la structure sont les mêmes lors de l'exécution de la méthode foo. Defaulta un destructeur par défaut implicite, donc nous créons un temporaire Defaultet fool'appelons. Nous passons cela à decltype pour déduire le type et déclarons qu'une variable n1 de ce type (int) est égale à 1. Nous voulons maintenant vérifier si foodans la structure NonDefault renvoie le même type mais comme il n'a pas de constructeur par défaut, comme nous discuté ci-dessus, nous avons recours à std :: declval ici qui nous renverra une référence rvalue deNonDefaultet permettent d'appeler foo sur l'objet temporaire. Nous faisons de même avec std::decltype et attribuons la variable n1à notre variable nouvellement déclarée n2. Le std::coutimprimera les valeurs 1 pour les deux variables.

Passons maintenant à l'exemple lui-même avec std::enable_if qui vérifie si nous avons 2 méthodes appelées begin()et end():

Dans ce code, nous utilisons std::declvalce qui permet de créer des objets temporaires de type Tsans constructeur par défaut, et de les utiliser decltypepour déterminer le type de leurs fonctions begin()et membres. end()Nous devons utiliser decltype afin d'utiliser declval , sinon ce n'est pas autorisé…

Nous utilisons ensuite std::is_samepour vérifier si ces types sont les mêmes. Si tel est le cas, le modèle de fonction est activé, sinon il est exclu de l'ensemble de surcharge.

Une limitation de SFINAE est qu'il ne fonctionne que pendant la déduction d'arguments de modèle, ce qui signifie qu'il ne peut pas être utilisé pour exclure conditionnellement des fonctions une fois que l'ensemble de surcharge a déjà été créé. C'est là if constexprqu'intervient.

si constexpr

if constexprest une nouvelle fonctionnalité introduite dans C++17 qui nous permet d'exécuter conditionnellement du code au moment de la compilation, basé sur une expression constante au moment de la compilation. Contrairement à SFINAE, if constexprpeut être utilisé pour exclure complètement le code de la compilation, même après la création du jeu de surcharge.

Par exemple, nous pouvons utiliser if constexprpour créer une fonction modèle qui calcule la factorielle d'un nombre, mais uniquement pour les types intégraux :

Dans ce code, nous utilisons if constexprpour exécuter conditionnellement du code selon qu'il Ts'agit d'un type intégral. Si Test entier, la fonction exécutera le calcul factoriel. Sinon, une assertion statique échouera, ce qui empêchera la compilation du code. Notez qu'il std::is_integral_vs'agit d'un raccourci pour std::is_integral<T>::value.

Nous pouvons également utiliser if constexprpour créer des fonctions de modèle plus complexes qui dépendent de plusieurs conditions. Par exemple, nous pouvons créer une fonction modèle qui n'accepte que les types qui ont à la fois une fonction membre begin()et une end()comme nous l'avons fait ci-dessus avec std::enable_if :

Dans l'exemple de code ci-dessus, nous créons un trait de type has_begin_endqui vérifie si un type Ta à la fois beginet enddes fonctions membres. nous utilisons std::declvalpour convertir le type Ten un type de référence, ce qui nous permet d'appeler des fonctions membres à l'intérieur decltyped'expressions sans avoir à construire un objet comme indiqué précédemment. Tout d'abord, nous appelons la fonction membre begin()sur std::declval<T>(), et si cela réussit, nous supprimons le résultat en utilisant l'opérateur virgule. Ensuite, nous essayons d'appeler end()on std::declval<T>(), qui, si cela réussit, nous obtenons le type de retour de using decltype. Notez que decltypec'est un must car on ne peut appeler des fonctions membres que sur des types de référence en utilisant declvalinside decltype. Enfin, si tout cela réussit, nous utilisons void_tpour retournervoid, sinon les paramètres de modèle de void_téchouent à évaluer et la spécialisation ne peut pas être résolue lors de la recherche de nom.

Un exemple omniprésent que nous pouvons également utiliser pour contraster les usages de S FINAE et constexpr , c'est pour implémenter l'algorithme factoriel

Exemple avec SFINAE

Dans le code ci-dessus, nous utilisons SFINAE pour exclure les types non intégraux de l'ensemble de surcharge de la factorialfonction en utilisant std::enable_ifavec le std::is_integral pour valider qu'il s'agit d'un nombre.

Exemple avec if constexpr

Nous utilisons if constexprpour exécuter conditionnellement le code de calcul factoriel si Test un type intégral. Si Tn'est pas un type intégral, la fonction échouera avec une assertion statique, ce qui empêchera la compilation du code.

Cas d'utilisation et recommandations

SFINAE et if constexpr sont tous deux des outils puissants qui peuvent être utilisés pour créer un code plus efficace et expressif. Cependant, ils ont des cas d'utilisation et des limites différents.

SFINAE est mieux utilisé lorsque vous souhaitez exclure conditionnellement des fonctions de l'ensemble de surcharge en fonction des propriétés des arguments de la fonction. Par exemple, vous pouvez utiliser SFINAE pour créer des modèles de fonction qui n'acceptent que certains types ou qui ont certaines propriétés. SFINAE est également utile lorsque vous souhaitez créer des fonctions de modèle plus complexes qui dépendent de plusieurs conditions.

if constexpr, en revanche, est mieux utilisé lorsque vous souhaitez exécuter du code de manière conditionnelle au moment de la compilation, en fonction d'expressions constantes au moment de la compilation. if constexprest particulièrement utile lorsque vous souhaitez créer un code plus concis et lisible, car la condition fait partie du corps de la fonction au lieu d'être répartie sur les paramètres du modèle. if constexprest également utile lorsque vous souhaitez fournir des messages d'erreur plus informatifs, car les assertions statiques peuvent être utilisées pour fournir des informations plus détaillées.

Lorsque vous décidez d'utiliser SFINAE ou if constexpr , tenez compte des points suivants :

  • Utilisez SFINAE lorsque vous souhaitez exclure conditionnellement des fonctions en fonction des propriétés des arguments de la fonction.
  • Utilisez if constexpr lorsque vous souhaitez exécuter conditionnellement du code basé sur des expressions constantes au moment de la compilation.
  • Utilisez SFINAE lorsque vous souhaitez créer des fonctions de modèle plus complexes qui dépendent de plusieurs conditions.
  • Utilisez if constexpr lorsque vous souhaitez créer un code plus concis et lisible, ou lorsque vous souhaitez fournir des messages d'erreur plus informatifs.
  • Considérez les limites de chaque approche, telles que le fait que SFINAE ne fonctionne que pendant la déduction d'arguments de modèle, ou que si constexpr nécessite la prise en charge de C++17.

SFINAE via l'utilisation de std::enable_if et if constexpr sont deux concepts importants en C++ qui permettent la création de code efficace et expressif. SFINAE est mieux utilisé lorsque vous souhaitez exclure conditionnellement des fonctions en fonction des propriétés des arguments de la fonction, tandis que if constexpr est mieux utilisé lorsque vous souhaitez exécuter conditionnellement du code basé sur des expressions constantes au moment de la compilation. Nous avons appris que nous pouvions utiliser declval avec delctype pour vérifier l'existence de fonctions membres spécifiques lorsque le type n'avait pas de constructeur par défaut. En comprenant les différences entre le SFINAE et if constexprapproches et leurs forces et faiblesses respectives, vous pouvez prendre de meilleures décisions lors de l'écriture de fonctions de modèle et de la création d'un code plus efficace et expressif.

Remarque : Pour la partie 1, vérifiezhttps://medium.com/@joao_vaz/template-meta-programming-in-c-17-primer-1b493a22d51

Photo de <a href="https://www.freepik.com/free-vector/business-decisions-concept-illustration_11392273.htm#query=choice&position=26&from_view=search&track=sph">Image par storyset</a> sur Freepik