92 votes

Réduire les conversions en C++0x. Est-ce que c'est moi, ou est-ce que cela ressemble à un changement de rupture ?

C++0x va rendre le code suivant et d'autres codes similaires mal formés, parce qu'ils requièrent un élément appelé réduction de la conversion d'un double à un 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 codes seront cassés par ce changement ? Est-ce que cela demande beaucoup d'efforts de corriger cela dans votre code, si votre code est affecté ?


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

Une conversion restrictive est une conversion implicite

  • d'un type à virgule flottante à un type entier, ou
  • d'un double long à un double ou à un flottant, ou d'un double à un flottant, sauf lorsque la source est une expression constante et que la valeur réelle après conversion se situe dans la plage des valeurs qui peuvent être représentées (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 spécifié à un type à virgule flottante, sauf lorsque la source est une expression constante et que la valeur réelle après conversion s'insère dans le type cible et produit la valeur originale lorsqu'elle est reconvertie dans le type d'origine ; ou
  • d'un type entier ou d'un type d'énumération non spécifié vers un type entier qui ne peut pas représenter toutes les valeurs du type original, sauf si la source est une expression constante et que la valeur réelle après conversion s'insère dans le type cible et produit la valeur originale lorsqu'elle est reconvertie dans le type original.

0 votes

J'espère que non, je ne vois pas ce type d'initialisation mais ils ont assuré qu'ils faisaient de leur mieux pour ne pas casser la base de code, C++0x a quand même de bonnes améliorations.

0 votes

@litb : Est-ce que cela sera aussi mal formé, ou est-ce que c'est seulement quand il y a une conversion qui a lieu ? int a[10] = {0};

1 votes

En supposant que cela ne soit valable que pour l'initialisation des types intégrés, je ne vois pas en quoi cela pourrait nuire. Bien sûr, cela peut casser du code. Mais cela devrait être facile à corriger.

42voto

Timothy003 Points 1015

J'ai rencontré ce changement de rupture lorsque j'ai utilisé GCC. Le compilateur imprimait une erreur pour un code comme celui-ci :

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 : conversion restrictive de (((long long unsigned int)i) & 4294967295ull) de long long unsigned int a unsigned int à l'intérieur { }

erreur : conversion restrictive de (((long long unsigned int)i) >> 32) de long long unsigned int a unsigned int à l'intérieur { }

Heureusement, les messages d'erreur étaient clairs et la solution é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 se trouvait dans une bibliothèque externe, avec seulement deux occurrences dans un fichier. Je ne pense pas que le changement de rupture affectera beaucoup de code. Les novices pourraient obtenir confus, cependant.

10voto

aschepler Points 23731

Je serais surpris et déçu d'apprendre que l'un des codes C++ que j'ai écrits au cours des 12 dernières années présentait ce genre de problème. Mais la plupart des compilateurs auraient craché des avertissements à propos de tout "rétrécissement" du temps de compilation depuis le début, à moins que je ne manque quelque chose.

Est-ce que cela réduit aussi les conversions ?

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

Si c'est le cas, je pense qu'ils pourraient se présenter un peu plus souvent que votre exemple du type flottant au type intégral.

1 votes

Je ne comprends pas pourquoi vous dites que ce ne serait pas une chose 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 2010 ?

9voto

Jed Points 900

Un exemple concret 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 double ce qui entraîne une promotion.

1 votes

Alors faites-le float en écrivant 0.5f . ;)

3 votes

@underscore_d Ne fonctionne pas si float était un paramètre de typedef ou de template (du moins sans perte de précision), mais le fait est que le code tel qu'il était é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".

7voto

Steve Jessop Points 166970

Je ne serais pas si surpris que quelqu'un se fasse prendre par quelque chose comme ça :

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, d'où le rétrécissement)

Je ne me souviens pas d'avoir écrit cela, cependant. C'est seulement utile si une approximation des limites est utile à 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, pour laquelle cela devrait compiler (et fonctionner, à condition que la perte de précision reste dans les limites acceptables pour le programme), alors que float asfloat[] = {val1, val2}; ne devrait pas ? Dans tous les cas, j'initialise deux flottants à partir de deux ints, c'est juste que dans un cas, les deux flottants sont membres d'un agrégat.

Cela semble particulièrement dur 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 destination et reconvertibles à 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, la solution consiste probablement à rendre la conversion explicite. A moins que vous ne fassiez quelque chose d'étrange avec les macros, je pense qu'un initialisateur de tableau n'apparaît qu'à proximité du type du tableau, ou au moins de quelque chose représentant le type, qui pourrait dépendre d'un paramètre de template. Donc un cast devrait être facile, bien que verbeux.

10 votes

"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.

4voto

hirschhornsalz Points 16306

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

J'ai eu une erreur avec un 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 étroite (ce qui est correct selon la norme). La raison en est que c y d sont implicitement promus à int et le résultat int n'est pas autorisé à être réduit à char dans une liste d'initialisation.

EN REVANCHE,

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

est bien sûr toujours bon (sinon, c'est l'enfer). 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 continue à penser qu'il s'agit d'un défaut du C++11, mais les personnes qui y travaillent pensent autrement - peut-être parce qu'il n'est pas facile à corriger sans se débarrasser de la conversion implicite des entiers (qui est une relique du passé, lorsque les gens écrivaient du code comme char a=b*c/d et s'attendait à ce qu'il fonctionne même si (b*c) > CHAR_MAX) ou en réduisant les erreurs de conversion (ce qui est peut-être une bonne chose).

0 votes

Je suis tombé sur ce qui suit, qui est une absurdité vraiment ennuyeuse : unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m }; <-- réduire la conversion à l'intérieur de { }. Vraiment ? Donc l'opérateur& convertit aussi implicitement les unsigned chars en int ? Eh bien je m'en fiche, le résultat est toujours garanti comme étant un unsigned char, argh.

0 votes

" conversion implicite des entiers Les " promotions " ?

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