Envoyé par
jo_link_noir
Je ne suis pas d'accord sur ce point, si je prends un UCHAR_MAX, pourquoi je ne voudrais pas la promotion ? Je connais les règles, je sais ce que cela va faire.
Simplement parce que tu n'as pas le choix!
Entre les deux codes suivants, indépendamment de tout débordement et de toute notion de constante:
1 2
| auto i = UCHAR_MAX;
unsigned short j = UCHAR_MAX; |
Où se trouve la promotion
Le type de i ne peut pas être autre chose, du fait de l'inférence de type, que ... unsigned char, vu que c'est le type normal de la valeur UCHAR_MAX, nous sommes bien d'accord
Si promotion il doit y avoir, cela ne peut se faire qu'au travers d'un unisgned short. Et elle ne peut donc avoir lieu que pour j
Autrement dit, tu ne peux pas avoir la promotion (le type unsigned short) et l'inférence de type en même temps, à moins bien sur de faire un cast explicite.
Ensuite, si 1 est une variable, quel est le "meilleur" type ? Mais pourquoi avoir une variable de type int ou mettre qui est finalement un int changerait quoi que se soit ?
Parce que l'on se trouve dans une situation dans laquelle nous voulons définir une constante de compilation, et que nous définissons en outre clairement le type de la donnée que l'on veut manipuler.
Pourquoi y aurait-il promotion d'une valeur connue pour être de type unsigned char (comme UCHAR_MAX) alors que je dis explicitement que je veux un unsigned char dans un code proche de
constexpr unsigned char i = UCHAR_MAX;
Et, si tu fais jouer l'inférence de type, c'est pareil:
constexpr auto i = UCHAR_MAX;
Il ne peut y avoir de promotion de UCHAR_MAX, vu que j'ai explicitement dit que je voulais le type associé à cette valeur.
Plusieurs conflits arrivent également avec cette proposition:
- Le type de l'expression ne peut plus être déterminé avec le type des opérantes: problème avec
std::common_type.
Ben si, il est défini par le contexte dans lequel l'expression est exécutée, ce qui est tout à fait normal, étant donné qu'une expresion constexpr -- et c'est mis dans la norme -- soit statique soit inline.
Envoyé par
norme 10.1.5 (dcl.constexpr)
1- The constexpr specifier shall be applied only to the definition of a variable or variable template or the declarationofafunctionorfunctiontemplate. Afunctionorstaticdatamemberdeclaredwiththe constexpr specifier is implicitly an inline function or variable (10.1.6). If any declaration of a function or function template has a constexpr specifier, then all its declarations shall contain the constexpr specifier.
- Dans la même veine, problème avec
decltype qui ne va pas retourner le même en utilisant des variables constpexr
Bien sur que si qu'il va retourner le même type
1 2
| template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u); |
Que T, U ou les deux soient constexpr ou non ne changera absolument rien à l'affaire
ou non ou des
declval<T>.
Idem. Que que T soit constexpr ou non, le type représenté par T ne change pas.
Je ne vois pas pourquoi cela poserait problème
Ou alors le type est toujours le même(1), mais le résultat sera faux dans un contexte constexpr(2).
(1) ben oui, il n'y a pas de raison qu'il en soit aurement
(2)A priori, non, et, si cela arrive, c'est qu'il y a un dépassement de valeur (dans un sens ou dans l'autre) par rapport au type défini par decltype, et il est donc normal d'avoir une erreur de compilation
- Que faire si une des opérantes est un type utilisateur qui surcharge + ?
Si l'utilisateur défini l'opérateur + sous la forme d'une constexpr, il est normal qu'il en subisse les même contrainte: dépassement de valeur (dans un sens ou dans l'autre) interdit.
- Que faire avec toutes les autres règle de conversion implicite sur les types utilisateurs ?
On ne les modifie pas. On se contente en gros de les ignorer dans un contexte de constexpr. Point barre.
Parce qu'en réalité, dans le contexte d'une constexpr, il n'y a que deux règles à prendre en compte:
1- la conversion doit être explicite dans le contexte, que ce soit sous une forme proche de
constexpr auto i = static_cast<some_type>(/* ... */);
ou sous la forme de
constexpr unsigned short j = /* ...*/;
2- l'expression de droite ne peut pas provoquer de dépassement de valeur au niveau du type défni à gauche.
- Le problème soulevé par Luc. Je me permets un exemple bien plus pernicieux:
1 2 3 4 5 6 7 8 9 10 11
|
constexpr auto foo(auto x) { return x+1; } // la même valeur avec le même type influence le résultat
template<auto max>
void bar(auto x)
{
using R = decltype(foo(max)); // constexpr, erreur possible ? (1)
R r = foo(x); // runtime, toujours ok (2)
// ....
decltype(foo(max)) r2 = foo(x); // ??? (3)
} |
(1) Effectivement, une erreur à la compilation est toujours possible ici, tout dépendant
- du type effectif de max
- de la valeur de max par rapport à l'intervalle autorisée par son type
(2) Dans le respect des règles définies, rien ne change: si c'était ok avant, c'est ok. Si cela amène un undefined behaviour (par exemple, parce que max n'est pas de type unsigned int), le même comportement indéfini continue à s'appliquer
(3) on n'est pas dans un contexte constexpr, même si foo est déclarée comme telle: rien ne change
Le problème n'est pas là, mais au niveau de l'expression qui inclut d'autres types avec des opérateurs: si tu veux le même type qu'un des opérateurs, `auto` n'est pas la solution. Ici, tu veux modifier les règles de promotion pour le type finale de l'expression soit différent. Cette proposition ne résout pas non plus les promotions depuis le même type
INT_MAX + INT_MAX -> int ou long (long) ?
A priori, je dirais:
erreur pour affectation à donnée auto ou de type de taille plus petite ou égale à un int à cause d'un dépassement
conversion pour affectation à donnée de tout type susceptible de représenter correctement la valeur de 2*INT_MAX
À mon sens, plutôt qu'ajouter de la complexité avec un comportement différent dans des contextes différents, il faudrait plutôt créer des types spécifiques qui s'occupent eux-mêmes de bloquer ou étendre les promotions.
On a déjà quinze type primitifs, répartis en deux grandes catégories (entier et réels) dont une catégorie se subdivise en deux saveurs se répartissant équitablement la plus grosse part du gâteau.
Combien de type primitifs nous faudra-t-il pour être content
S'il y avait la moindre chance que la proposition passe, je proposerais bien de régler une bonne fois pour toute le problème de comportement indéfini en disant que si un dépassement de valeur est remarquable à la compilation, cela doit obligatoirement provoquer une erreur, et que tout dépassement de valeur à l'exécution doit faire pareil.
Mais une telle proposition sera recue à coups de lancers de pierres!!!!
Il y a juste un truc qui me choque, c'est le comportement de
std::integral_constant<char, 999999> qui compile sur msvc et icc. J'ai bien plus l'habitude de voir une erreur. C'est plutôt sur ce point qu'il faudrait expliciter les règles (ce qui corrigerait le problème de index_impl de la proposition). Il y a le même problème avec
if constexpr (2) que tout le monde accepte, sauf clang.
C'est justement ce genre de chose que je veux régler. Mais, a priori, ce ne pourra être fait qu'au niveau des constexpr, qui sont malgré tout "relativement" récentes et en évolution constante depuis qu'elles sont arrivées.
Le fait de les rendre purement contextuelles (ce qu'elles sont déjà en pratique, d'après le standard) est à mon sens la solution la plus simple à mettre en oeuvre.
Je ne prétend pas avoir couvert tous les aspects à prendre en compte (le mot constexpr apparait déjà plus de 999 fois dans la norme, et on peut présumer que ce sont surtout les endroits où il n'apparait pas qu'il faudrait en parler ).
Mais si on ne propose pas un début de solution à ce niveau là, on peut être sur que les choses n'évolueront jamais
Partager