54 votes

Pourquoi faut-il marquer les fonctions comme constexpr ?

C++11 permet aux fonctions déclarées avec l'attribut constexpr à utiliser dans des expressions constantes telles que les arguments de modèles. Il y a des exigences strictes sur ce qui peut être utilisé dans les expressions constantes. constexpr ; essentiellement, une telle fonction n'encapsule qu'une seule sous-expression et rien d'autre.

Si c'est si difficile de faire une fonction qui est constexpr et il est si facile de dire qu'une fonction ne l'est pas, pourquoi exiger le mot-clé ? Que gagne-t-on ?

Elle aide à révéler l'intention d'une interface, mais elle n'a pas garantissent qu'une fonction est utilisable dans des expressions constantes. Après avoir écrit une constexpr le programmeur doit encore écrire un scénario de test ou s'assurer qu'elle est effectivement utilisée comme telle. La présence du mot-clé peut donc donner un faux sentiment de sécurité, car le compilateur vérifie que les contraintes syntaxiques tangentielles sont respectées tout en ignorant la contrainte sémantique centrale.

Bien sûr, constexpr dans les déclarations d'objets est très utile. L'initialisateur doit être une expression constante de bonne foi, c'est ainsi que nous testons constexpr fonctions. ( Règle de style : chaque constexpr doit être suivie d'un constexpr ou static_assert test-case).


En bref : Y aurait-il un effet indésirable sur la langue si constexpr dans les déclarations de fonctions étaient simplement facultatives ? Ou y aurait-il un effet quelconque sur tout programme valide ?

44voto

Tony D Points 43962

Empêcher le code client de s'attendre à plus que ce que vous promettez

Disons que j'écris une bibliothèque et que j'y ai une fonction qui renvoie une constante :

inline int f() { return 4; }

Si constexpr n'était pas nécessaire, vous - en tant qu'auteur du code client - pourriez partir et faire quelque chose comme ceci :

int my_array[f()];

Alors je dois changer f() pour dire renvoyer la valeur d'un fichier de configuration, votre code client serait cassé, mais je n'aurais aucune idée que j'ai risqué de casser votre code. En effet, il se peut que ce ne soit que lorsque vous rencontrez un problème de production et que vous recompilez que vous trouvez ce problème supplémentaire qui vous empêche de reconstruire.

En utilisant constexpr je documente le fait que le code client peut raisonnablement s'attendre à ce que la fonction reste un élément de l'environnement. constexpr et l'utiliser comme tel.

Parce que le compilateur a constexpr il est en mesure de garantir que le code du client n'est pas construit pour dépendre d'un système non compatible avec l'UE. constexpr selon le scénario ci-dessus - c'est plus que de la documentation : c'est une application au moment de la compilation.

Pourquoi n'y a-t-il pas de diagnostic pour les fonctions "évidemment" jamais constantes ?

Je pense que la confusion ici est due à constexpr ne garantit pas de manière proactive qu'il existe un ensemble d'arguments pour lequel le résultat est effectivement constant au moment de la compilation : il demande plutôt au programmeur d'en prendre la responsabilité (sinon, le §7.1.5/5 de la norme considère que le programme est mal formé mais ne demande pas au compilateur d'émettre un diagnostic). Oui, c'est malheureux, mais cela n'enlève pas l'utilité de constexpr .

Donc, peut-être que la question ne devrait pas être "quel est l'intérêt de". constexpr ", mais "pourquoi puis-je compiler une constexpr qui ne peut jamais retourner une valeur constante". Réponse : parce qu'il serait nécessaire de procéder à une analyse exhaustive des branches qui pourrait impliquer un nombre illimité de combinaisons. Cela pourrait être excessivement coûteux en temps de compilation et/ou en mémoire - même au-delà de la capacité de tout matériel imaginable - à diagnostiquer. De plus, même lorsque cela s'avère pratique, le fait de devoir diagnostiquer de tels cas avec précision est une toute nouvelle boîte de Pandore pour les auteurs de compilateurs (qui sont déjà suffisamment occupés avec l'implémentation de C++11). Il y aurait également des implications pour le programme, telles que la définition des fonctions appelées depuis l'intérieur de l'élément constexpr qui doit être visible lorsque la validation a été effectuée (et les fonctions que la fonction appelle, etc.).

En attendant, absence de de constexpr continue d'interdire l'utilisation comme valeur const : la rigueur est sur le sans- constexpr côté. C'est utile comme illustré ci-dessus.

Comparaison avec les fonctions membres non "constantes".

  • constexpr empêche int x[f()] alors que le manque de const empêche const X x; x.f(); - ils sont tous les deux s'assurer que le code client ne code pas en dur des dépendances non désirées

  • dans les deux cas, vous ne voudriez pas que le compilateur détermine const[expr] -ness automatiquement :

    • vous ne voudriez pas que le code client appelle une fonction membre sur une const alors que l'on peut déjà anticiper que cette fonction évoluera pour modifier la valeur observable, cassant ainsi le code client.

    • vous ne voudriez pas qu'une valeur soit utilisée comme paramètre de modèle ou dimension de tableau si vous prévoyez déjà qu'elle sera déterminée plus tard au moment de l'exécution.

  • ils différer dans la mesure où le compilateur impose const l'utilisation d'autres membres au sein d'une const mais n'impose pas de résultat constant au moment de la compilation avec la fonction membre constexpr (en raison des limitations pratiques du compilateur)

19voto

Potatoswatter Points 70305

Lorsque J'ai appuyé Richard Smith, un auteur de Clang, a expliqué :

Le mot-clé constexpr a une utilité.

Il affecte le moment où une spécialisation de modèle de fonction est instanciée (les spécialisations de modèle de fonction constexpr peuvent avoir besoin d'être instanciées si elles sont appelées dans des contextes non évalués ; il n'en va pas de même pour les fonctions non constexpr puisqu'un appel à l'une d'entre elles ne peut jamais faire partie d'une expression constante). Si nous supprimions la signification du mot-clé, nous devrions instancier un tas de spécialisations supplémentaires, juste au cas où l'appel serait une expression constante.

Il réduit le temps de compilation en limitant l'ensemble des appels de fonction que les implémentations doivent essayer d'évaluer pendant la traduction. (Ceci est important pour les contextes où les implémentations doivent essayer d'évaluer des expressions constantes, mais ce n'est pas une erreur si une telle évaluation échoue -- en particulier, les initialisateurs d'objets de durée de stockage statique).

Tout cela ne semblait pas convaincant au départ, mais si l'on s'attarde sur les détails, les choses se dénouent sans constexpr . Une fonction ne doit pas être instanciée avant d'être utilisée en ODR, ce qui signifie essentiellement qu'elle est utilisée au moment de l'exécution. Ce qui est spécial dans constexpr est qu'elles peuvent violer cette règle et nécessiter quand même une instanciation.

L'instanciation de la fonction est une procédure récursive. L'instanciation d'une fonction entraîne l'instanciation des fonctions et des classes qu'elle utilise, quels que soient les arguments d'un appel particulier.

Si quelque chose se passait mal lors de l'instanciation de cet arbre de dépendance (potentiellement à un coût important), il serait difficile d'avaler l'erreur. De plus, l'instanciation des modèles de classe peut avoir des effets secondaires à l'exécution.

Étant donné un appel de fonction dépendant d'un argument au moment de la compilation dans une signature de fonction, la résolution de surcharge peut entraîner l'instanciation de définitions de fonctions simplement auxiliaires à celles de l'ensemble de surcharge, y compris les fonctions qui ne sont même pas appelées. De telles instanciations peuvent avoir des effets secondaires, y compris des problèmes de forme et de comportement à l'exécution.

Il s'agit d'un cas particulier, mais de mauvaises choses peuvent se produire si vous n'exigez pas que les gens choisissent de s'inscrire sur le site Web de la Commission européenne. constexpr fonctions.

3voto

Jesse Good Points 22971

Sans ce mot-clé, le compilateur ne peut pas diagnostiquer les erreurs. Le compilateur ne serait pas en mesure de vous dire que la fonction est syntaxiquement invalide en tant que constexpr . Bien que vous ayez dit que cela procure un "faux sentiment de sécurité", je pense qu'il est préférable de détecter ces erreurs le plus tôt possible.

3voto

iammilind Points 29275

Nous pouvons vivre sans constexpr mais dans certains cas, cela rend le code plus facile et intuitif.
Par exemple, nous avons une classe qui déclare un tableau avec une certaine longueur de référence :

template<typename T, size_t SIZE>
struct MyArray
{
  T a[SIZE];
};

Par convention, on pourrait déclarer MyArray comme :

int a1[100];
MyArray<decltype(*a1), sizeof(a1)/sizeof(decltype(a1[0]))> obj;

Maintenant, voyons comment cela se passe avec constexpr :

template<typename T, size_t SIZE>
constexpr
size_t getSize (const T (&a)[SIZE]) { return SIZE; }

int a1[100];
MyArray<decltype(*a1), getSize(a1)> obj;

En bref, toute fonction (par ex. getSize(a1) ) peut être utilisé comme argument de template seulement si le compilateur le reconnaît comme constexpr .

constexpr est également utilisé pour vérifier la logique négative. Il assure qu'un objet donné est au moment de la compilation. Voici la référence lien par exemple

int i = 5;
const int j = i; // ok, but `j` is not at compile time
constexprt int k = i; // error

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X