Réduire les conversions dans C ++ 0x. Est-ce juste moi, ou est-ce que cela ressemble à un changement radical?

85

C ++ 0x va rendre le code suivant et le code similaire mal formé, car il nécessite une conversion dite restrictive de a doubleen a int.

int a[] = { 1.0 };

Je me demande si ce type d'initialisation est beaucoup utilisé dans le code du monde réel. Combien de code sera brisé par ce changement? Y a-t-il beaucoup d'efforts pour résoudre ce problème dans votre code, si votre code est affecté du tout?


Pour référence, voir 8.5.4 / 6 de n3225

Une conversion restrictive est une conversion implicite

  • d'un type à virgule flottante à un type entier, ou
  • de long double à double ou float, ou de double à float, sauf lorsque la source est une expression constante et que la valeur réelle après conversion se situe dans la plage de valeurs pouvant être représentée (même si elle ne peut pas être représentée exactement), ou
  • d'un type entier ou d'un type d'énumération non scopé à un type à virgule flottante, sauf lorsque la source est une expression constante et que la valeur réelle après conversion rentrera dans le type cible et produira la valeur d'origine lorsqu'elle sera reconvertie au type d'origine, ou
  • d'un type entier ou d'un type d'énumération non scopé à un type entier qui ne peut pas représenter toutes les valeurs du type d'origine, sauf lorsque la source est une expression constante et la valeur réelle après la conversion rentrera dans le type cible et produira la valeur d'origine lorsque reconverti au type d'origine.
Johannes Schaub - litb
la source
1
En supposant que cela n'est valable que pour l'initialisation des types intégrés, je ne vois pas comment cela nuirait. Bien sûr, cela peut casser du code. Mais devrait être facile à réparer.
Johan Kotlinski
1
@John Dibling: Non, l'initialisation n'est pas mal formée lorsque la valeur peut être exactement représentée par le type de cible. (Et 0c'est déjà un de inttoute façon.)
aschepler
2
@Nim: Notez que ce n'est que mal formé dans {les initialiseurs d'accolades }, et que la seule utilisation héritée de ceux-ci est pour les tableaux et les structures POD. De plus, si le code existant a des casts explicites à leur place, il ne se cassera pas.
aschepler
4
@j_random_hacker comme le dit le document de travail, int a = 1.0;est toujours valide.
Johannes Schaub - litb
1
@litb: Merci. En fait, je trouve cela compréhensible mais décevant - à mon humble avis, il aurait été bien préférable d'exiger une syntaxe explicite pour toutes les conversions restrictives dès le début de C ++.
j_random_hacker

Réponses:

41

J'ai rencontré ce changement radical lorsque j'ai utilisé GCC. Le compilateur a imprimé une erreur pour le code comme ceci:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}

En fonction void foo(const long long unsigned int&):

Erreur: la conversion de rétrécissement (((long long unsigned int)i) & 4294967295ull)de long long unsigned intà l' unsigned intintérieur {}

Erreur: la conversion de rétrécissement (((long long unsigned int)i) >> 32)de long long unsigned intà l' unsigned intintérieur {}

Heureusement, les messages d'erreur étaient simples et le correctif était simple:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
            static_cast<unsigned int>(i >> 32)};
}

Le code était dans une bibliothèque externe, avec seulement deux occurrences dans un fichier. Je ne pense pas que le changement de rupture affectera beaucoup le code. Les novices pourraient cependant être confus .

Timothy003
la source
9

Je serais surpris et déçu de moi-même d'apprendre que l'un des codes C ++ que j'ai écrits au cours des 12 dernières années avait ce genre de problème. Mais la plupart des compilateurs auraient émis des avertissements sur tout "rétrécissement" au moment de la compilation, à moins que je ne manque quelque chose.

S'agit-il également de conversions réductrices?

unsigned short b[] = { -1, INT_MAX };

Si tel est le cas, je pense qu'ils pourraient apparaître un peu plus souvent que votre exemple de type flottant à type intégral.

aschepler
la source
1
Je ne comprends pas pourquoi vous dites que ce serait une chose pas rare à trouver dans le code. Quelle est la logique entre l'utilisation de -1 ou INT_MAX au lieu de USHRT_MAX? USHRT_MAX n'était-il pas dans les climats à la fin de 2010?
7

Je ne serais pas surpris si quelqu'un se faisait prendre par quelque chose comme:

float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};

(sur mon implémentation, les deux derniers ne produisent pas le même résultat lorsqu'ils sont reconvertis en int / long, donc se rétrécissent)

Cependant, je ne me souviens pas avoir écrit cela. Ce n'est utile que si une approximation des limites est utile pour quelque chose.

Cela semble au moins vaguement plausible aussi:

void some_function(int val1, int val2) {
    float asfloat[] = {val1, val2};    // not in C++0x
    double asdouble[] = {val1, val2};  // not in C++0x
    int asint[] = {val1, val2};        // OK
    // now do something with the arrays
}

mais ce n'est pas tout à fait convaincant, car si je sais que j'ai exactement deux valeurs, pourquoi les mettre dans des tableaux plutôt que simplement float floatval1 = val1, floatval1 = val2;? Quelle est la motivation, cependant, pourquoi cela devrait compiler (et fonctionner, à condition que la perte de précision soit dans une précision acceptable pour le programme), alors que float asfloat[] = {val1, val2};non? Quoi qu'il en soit, j'initialise deux flottants à partir de deux entiers, c'est juste que dans un cas, les deux flottants sont membres d'un agrégat.

Cela semble particulièrement sévère dans les cas où une expression non constante entraîne une conversion restrictive même si (sur une implémentation particulière), toutes les valeurs du type source sont représentables dans le type de destination et convertibles à leurs valeurs d'origine:

char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?

En supposant qu'il n'y ait pas de bogue, le correctif est probablement toujours de rendre la conversion explicite. À moins que vous ne fassiez quelque chose d'étrange avec les macros, je pense qu'un initialiseur de tableau n'apparaît qu'à proximité du type du tableau, ou du moins de quelque chose représentant le type, qui pourrait dépendre d'un paramètre de modèle. Donc, un casting devrait être facile, même si détaillé.

Steve Jessop
la source
8
"si je sais que j'ai exactement deux valeurs, pourquoi les mettre dans des tableaux" - par exemple parce qu'une API comme OpenGL l'exige.
Georg Fritzsche
7

Un exemple pratique que j'ai rencontré:

float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};

Le littéral numérique est implicitement doublece qui provoque la promotion.

Jed
la source
1
alors faites-le floaten écrivant 0.5f. ;)
underscore_d
1
@underscore_d Ne fonctionne pas s'il floats'agissait d'un paramètre typedef ou template (au moins sans perte de précision), mais le fait est que le code tel qu'il est écrit fonctionnait avec la sémantique correcte et est devenu une erreur avec C ++ 11. C'est-à-dire la définition d'un «changement de rupture».
Jed
5

Essayez d'ajouter -Wno-narrowing à votre CFLAGS, par exemple:

CFLAGS += -std=c++0x -Wno-narrowing
Kukuh Indrayana
la source
ou CPPFLAGS dans le cas des compilateurs C ++ (bien sûr cela dépend de votre système de construction ou de votre Makefile)
Mikolasan
4

Les erreurs de conversion restrictives interagissent mal avec les règles de promotion d'entiers implicites.

J'ai eu une erreur avec le code qui ressemblait à

struct char_t {
    char a;
}

void function(char c, char d) {
    char_t a = { c+d };
}

Ce qui produit une erreur de conversion de rétrécissement (qui est correcte selon la norme). La raison en est que cet dimplicitement être promu vers intet que le résultat intn'est pas autorisé à être réduit à char dans une liste d'initialiseurs.

OTOH

void function(char c, char d) {
    char a = c+d;
}

est bien sûr toujours bien (sinon tout l'enfer se déchaînerait). Mais étonnamment, même

template<char c, char d>
void function() {
    char_t a = { c+d };
}

est ok et compile sans avertissement si la somme de c et d est inférieure à CHAR_MAX. Je pense toujours que c'est un défaut de C ++ 11, mais les gens pensent le contraire - peut-être parce qu'il n'est pas facile à réparer sans se débarrasser de l'une ou l'autre des conversions entières implicites (qui est une relique du passé, lorsque les gens ont écrit du code comme char a=b*c/det je m'attendais à ce que cela fonctionne même si (b * c)> CHAR_MAX) ou en réduisant les erreurs de conversion (ce qui est peut-être une bonne chose).

Gunther Piez
la source
J'ai rencontré ce qui suit qui est vraiment un non-sens ennuyeux: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- rétrécissement de la conversion à l'intérieur {}. Vraiment? Ainsi, l'opérateur & convertit également implicitement les caractères non signés en int? Eh bien, je m'en fiche, le résultat est toujours garanti comme étant un caractère non signé, argh.
Carlo Wood
" Conversion entier implicite des promotions"?
curiousguy
2

C'était en effet un changement radical car l'expérience réelle de cette fonctionnalité a montré que gcc avait transformé le rétrécissement en un avertissement d'une erreur dans de nombreux cas en raison de problèmes réels liés au portage des bases de code C ++ 03 vers C ++ 11. Voir ce commentaire dans un rapport de bogue gcc :

La norme exige seulement qu '"une implémentation conforme doit émettre au moins un message de diagnostic", de sorte que la compilation du programme avec un avertissement est autorisée. Comme l'a dit Andrew, -Werror = rétrécissement vous permet d'en faire une erreur si vous le souhaitez.

G ++ 4.6 a donné une erreur, mais il a été changé intentionnellement en un avertissement pour 4.7 parce que de nombreuses personnes (moi y compris) ont trouvé que la réduction des conversions était l'un des problèmes les plus fréquemment rencontrés lors de la tentative de compilation de grandes bases de code C ++ 03 en C ++ 11 . Code déjà bien formé tel que char c [] = {i, 0}; (où i ne sera jamais que dans la plage de char) a causé des erreurs et a dû être changé en char c [] = {(char) i, 0}

Shafik Yaghmour
la source
1

Il semble que GCC-4.7 ne donne plus d'erreurs pour réduire les conversions, mais des avertissements à la place.

kyku
la source