web-dev-qa-db-fra.com

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

C++ 0x va rendre le code suivant et le code similaire mal formés, car il nécessite une soi-disant conversion restrictive 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 code sera cassé par ce changement? Est-ce que beaucoup d'effort est nécessaire pour résoudre ce problème dans votre code, si votre code est affecté?


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 en float, sauf si 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é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 épars à un type à virgule flottante, sauf si la source est une expression constante et que la valeur réelle après la conversion tiendra dans le type cible et produira la valeur d'origine lors de la reconversion au type d'origine, ou
  • d'un type entier ou d'un type d'énumération non limité à un type entier qui ne peut pas représenter toutes les valeurs du type d'origine, sauf si la source est une expression constante et que la valeur réelle après la conversion tiendra dans le type cible et produira la valeur d'origine lorsque reconverti au type d'origine.
81

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

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

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 se trouvait dans une bibliothèque externe, avec seulement deux occurrences dans un fichier. Je ne pense pas que le changement décisif affectera beaucoup de code. Les novices pourraient obtenirconfus, si.

41
Timothy003

Je serais surpris et déçu d'apprendre par moi-même que tout le code C++ que j'ai écrit au cours des 12 dernières années avait ce type de problème. Mais la plupart des compilateurs auraient lancé des avertissements à propos de toute "réduction" au moment de la compilation, à moins que quelque chose ne me manque.

S'agit-il également d'une réduction du nombre de conversions?

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.

9
aschepler

Je ne serais pas si surpris que quelqu'un se fasse prendre à 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 quand ils sont reconvertis en int/long, donc se rétrécissent)

Je ne me souviens pas avoir écrit cela, cependant. 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 travailler, à condition que la perte de précision est dans l'exactitude acceptable pour le programme), tandis que float asfloat[] = {val1, val2}; ne devrait pas? De toute façon, j'initialise deux flottants à partir de deux ints, c'est simplement 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 (dans une implémentation particulière), toutes les valeurs du type source sont représentables dans le type de destination et peuvent être reconverties à 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 consiste probablement à rendre la conversion explicite. Sauf si vous faites quelque chose de bizarre avec les macros, je pense qu'un initialiseur de tableau n'apparaît que près 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, si prolixe.

7
Steve Jessop

Essayez d’ajouter -Wno-rétrécissement à vos CFLAGS, par exemple:

CFLAGS += -std=c++0x -Wno-narrowing
5
Kukuh Indrayana

Un cas 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 double, ce qui provoque la promotion.

5
Jed

Les erreurs de conversion réduites interagissent mal avec les règles de promotion de nombre entier implicite.

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 restrictive (qui est correcte selon la norme). La raison en est que c et d sont implicitement promus à int et que int résultant ne peut pas être réduit à char dans une liste d'initialisation. .

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 de là-bas pensent le contraire - peut-être parce que ce n’est pas facile à corriger sans se débarrasser de la conversion d’entier implicite (ce qui est un vestige du passé, quand les gens écrivaient du code comme char a=b*c/d et s’attend à ce qu’il fonctionne même si (b * c)> CHAR_MAX) ou à réduire les erreurs de conversion (qui sont peut-être une bonne chose).

4
Gunther Piez

C’était en effet un changement radical, car l’expérience réelle de cette fonctionnalité a montré que gcc était devenu un avertissement suite à une erreur dans de nombreux cas en raison de difficultés réelles liées au portage de bases de code C++ 03 en C++ 11. Voir ce commentaire dans un rapport de bogue gcc :

La norme exige seulement qu '"une mise en œuvre conforme émette au moins un message de diagnostic", de sorte que la compilation du programme avec un avertissement est autorisée. Comme Andrew a dit, -Werror = rétrécissement vous permet de faire une erreur si vous voulez.

G ++ 4.6 a donné une erreur mais elle a été changée en avertissement intentionnellement pour 4.7 car de nombreuses personnes (moi-même inclus) ont découvert que le fait de réduire les conversions était l'un des plus courants. rencontré des problèmes lors de la tentative de compilation de bases de code C++ 03 volumineuses en tant que C++ 11 . Code précédemment bien formé tel que char c [] = {i, 0}; (où je ne serai jamais que dans la plage de char) a causé des erreurs et a dû être remplacé par char c [] = {(char) i, 0}

1
Shafik Yaghmour

Il semble que GCC-4.7 ne génère plus d'erreurs pour limiter les conversions, mais des avertissements.

1
kyku