Eingrenzen von Konvertierungen in C ++ 0x. Bin ich es nur, oder klingt das nach einer bahnbrechenden Veränderung?

84

C ++ 0x wird den folgenden Code und ähnlichen Code falsch formulieren, da eine sogenannte Verengungskonvertierung von a doublein a erforderlich ist int.

int a[] = { 1.0 };

Ich frage mich, ob diese Art der Initialisierung im Code der realen Welt häufig verwendet wird. Wie viele Codes werden durch diese Änderung beschädigt? Ist es sehr aufwendig, dies in Ihrem Code zu beheben, wenn Ihr Code überhaupt betroffen ist?


Als Referenz siehe 8.5.4 / 6 von n3225

Eine Verengungskonvertierung ist eine implizite Konvertierung

  • von einem Gleitkommatyp zu einem ganzzahligen Typ oder
  • von long double zu double oder float oder von double zu float, außer wenn die Quelle ein konstanter Ausdruck ist und der tatsächliche Wert nach der Konvertierung innerhalb des Wertebereichs liegt, der dargestellt werden kann (auch wenn er nicht genau dargestellt werden kann), oder
  • von einem Integer-Typ oder einem Aufzählungstyp ohne Gültigkeitsbereich zu einem Fließkomma-Typ, außer wenn die Quelle ein konstanter Ausdruck ist und der tatsächliche Wert nach der Konvertierung in den Zieltyp passt und den ursprünglichen Wert erzeugt, wenn er zurück in den ursprünglichen Typ konvertiert wird, oder
  • Von einem Integer-Typ oder einem Aufzählungstyp ohne Gültigkeitsbereich zu einem Integer-Typ, der nicht alle Werte des Originaltyps darstellen kann, es sei denn, die Quelle ist ein konstanter Ausdruck und der tatsächliche Wert nach der Konvertierung passt in den Zieltyp und erzeugt den Originalwert, wenn zurück zum ursprünglichen Typ konvertiert.
Johannes Schaub - litb
quelle
1
Angenommen, dies gilt nur für die Initialisierung eingebauter Typen, kann ich nicht sehen, wie dies schaden würde. Sicher, dies kann einen Code beschädigen. Sollte aber leicht zu beheben sein.
Johan Kotlinski
1
@ John Dibling: Nein, die Initialisierung ist nicht fehlerhaft, wenn der Wert durch den Zieltyp genau dargestellt werden kann. (Und 0ist intsowieso schon ein .)
aschepler
2
@Nim: Beachten Sie, dass dies nur in {geschweiften Klammerinitialisierern schlecht ausgebildet }ist und diese nur für Arrays und POD-Strukturen verwendet werden. Wenn vorhandener Code explizite Umwandlungen aufweist, zu denen er gehört, wird er nicht beschädigt.
Aschepler
4
@j_random_hacker, wie das Arbeitspapier sagt, int a = 1.0;ist immer noch gültig.
Johannes Schaub - Litb
1
@litb: Danke. Eigentlich finde ich das verständlich, aber enttäuschend - meiner Meinung nach wäre es viel besser gewesen, von Anfang an eine explizite Syntax für alle einschränkenden Konvertierungen von C ++ zu verlangen.
j_random_hacker

Antworten:

41

Ich bin auf diese bahnbrechende Veränderung gestoßen, als ich GCC verwendet habe. Der Compiler hat einen Fehler für Code wie diesen gedruckt:

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

In Funktion void foo(const long long unsigned int&):

Fehler: Eingrenzung der Konvertierung von (((long long unsigned int)i) & 4294967295ull)von long long unsigned intnach unsigned intinnen {}

Fehler: Eingrenzung der Konvertierung von (((long long unsigned int)i) >> 32)von long long unsigned intnach unsigned intinnen {}

Glücklicherweise waren die Fehlermeldungen unkompliziert und die Korrektur war einfach:

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

Der Code befand sich in einer externen Bibliothek mit nur zwei Vorkommen in einer Datei. Ich denke nicht, dass die bahnbrechende Änderung viel Code beeinflussen wird. Anfänger könnten jedoch verwirrt werden .

Timothy003
quelle
9

Ich wäre überrascht und enttäuscht von mir selbst zu erfahren, dass jeder der C ++ - Codes, die ich in den letzten 12 Jahren geschrieben habe, diese Art von Problem hatte. Aber die meisten Compiler hätten die ganze Zeit über Warnungen vor "Einengungen" während der Kompilierung ausgegeben, es sei denn, ich vermisse etwas.

Verengen diese auch die Conversions?

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

Wenn ja, denke ich, dass sie etwas häufiger auftreten als Ihr Beispiel vom Typ Floating vom Typ Integral.

aschepler
quelle
1
Ich verstehe nicht, warum Sie sagen, dass dies im Code nicht ungewöhnlich ist. Welche Logik besteht zwischen der Verwendung von -1 oder INT_MAX anstelle von USHRT_MAX? War USHRT_MAX Ende 2010 nicht in Klimazonen?
7

Ich wäre nicht allzu überrascht, wenn jemand von so etwas wie:

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

(Bei meiner Implementierung führen die letzten beiden nicht zum gleichen Ergebnis, wenn sie zurück in int / long konvertiert werden. Daher werden sie enger.)

Ich kann mich jedoch nicht erinnern, dies jemals geschrieben zu haben. Es ist nur nützlich, wenn eine Annäherung an die Grenzen für etwas nützlich ist.

Dies scheint zumindest auch vage plausibel:

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
}

aber es ist nicht ganz überzeugend, denn wenn ich weiß, dass ich genau zwei Werte habe, warum sollte ich sie dann in Arrays einfügen und nicht nur float floatval1 = val1, floatval1 = val2;? Was ist die Motivation, warum das kompiliert werden sollte (und funktionieren sollte, vorausgesetzt, der Genauigkeitsverlust liegt innerhalb der für das Programm akzeptablen Genauigkeit), während dies float asfloat[] = {val1, val2};nicht der Fall sein sollte? So oder so initialisiere ich zwei Floats aus zwei Ints. In einem Fall sind die beiden Floats nur Mitglieder eines Aggregats.

Dies erscheint besonders hart in Fällen, in denen ein nicht konstanter Ausdruck zu einer engeren Konvertierung führt, obwohl (bei einer bestimmten Implementierung) alle Werte des Quelltyps im Zieltyp darstellbar und wieder in ihre ursprünglichen Werte konvertierbar sind:

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

Vorausgesetzt, es gibt keinen Fehler, besteht die Lösung vermutlich immer darin, die Konvertierung explizit zu machen. Sofern Sie mit Makros nichts Seltsames tun, wird ein Array-Initialisierer meiner Meinung nach nur in der Nähe des Array-Typs oder zumindest in der Nähe des Typs angezeigt, der von einem Vorlagenparameter abhängen könnte. Eine Besetzung sollte also einfach sein, wenn sie ausführlich ist.

Steve Jessop
quelle
8
"Wenn ich weiß, dass ich genau zwei Werte habe, warum sie dann in Arrays einfügen" - z. B. weil eine API wie OpenGL dies erfordert.
Georg Fritzsche
7

Ein praktisches Beispiel, dem ich begegnet bin:

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

Das numerische Literal ist implizit das, doublewas Werbung fördert.

Jed
quelle
1
Also mach es floatdurch Schreiben 0.5f. ;)
underscore_d
1
@underscore_d Funktioniert nicht, wenn floates sich um einen typedef- oder template-Parameter handelt (zumindest ohne Genauigkeitsverlust), aber der Punkt ist, dass der geschriebene Code mit der richtigen Semantik funktioniert und mit C ++ 11 zu einem Fehler geworden ist. Dh die Definition einer "brechenden Veränderung".
Jed
5

Versuchen Sie, Ihren CFLAGS -Wno-Narrowing hinzuzufügen, zum Beispiel:

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

Eingrenzende Konvertierungsfehler interagieren schlecht mit impliziten Regeln für die Ganzzahl-Promotion.

Ich hatte einen Fehler mit dem Code, der aussah

struct char_t {
    char a;
}

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

Dies führt zu einem sich verengenden Konvertierungsfehler (der gemäß dem Standard korrekt ist). Der Grund dafür ist , dass cund dimplizit befördert zu werden, intund die resultierende intdarf nicht wieder auf char in einer Initialisiererliste verengt werden.

OTOH

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

ist natürlich noch in Ordnung (sonst würde die Hölle losbrechen). Aber überraschenderweise sogar

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

ist in Ordnung und wird ohne Warnung kompiliert, wenn die Summe von c und d kleiner als CHAR_MAX ist. Ich denke immer noch, dass dies ein Fehler in C ++ 11 ist, aber die Leute dort denken anders - möglicherweise, weil es nicht einfach ist, ihn zu beheben, ohne die implizite Ganzzahlkonvertierung loszuwerden (was ein Relikt aus der Vergangenheit ist, als die Leute Code geschrieben haben wie char a=b*c/dund erwartet, dass es funktioniert, auch wenn (b * c)> CHAR_MAX) oder Konvertierungsfehler eingrenzen (die möglicherweise eine gute Sache sind).

Gunther Piez
quelle
Ich bin auf Folgendes gestoßen, was wirklich nervigen Unsinn ist: unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<- Eingrenzung der Konvertierung in {}. "Ja wirklich?" Also konvertiert der Operator & auch implizit vorzeichenlose Zeichen in int? Nun, es ist mir egal, das Ergebnis ist garantiert immer noch ein Zeichen ohne Vorzeichen, argh.
Carlo Wood
""Aktionen zur impliziten Ganzzahlkonvertierung "?
Neugieriger
2

Es war in der Tat eine bahnbrechende Änderung, da die reale Erfahrung mit dieser Funktion gezeigt hat, dass gcc die Verengung in vielen Fällen zu einer Warnung aufgrund eines Fehlers gemacht hat, der auf reale Probleme bei der Portierung von C ++ 03-Codebasen auf C ++ 11 zurückzuführen ist. Siehe diesen Kommentar in einem gcc-Fehlerbericht :

Der Standard verlangt lediglich, dass "eine konforme Implementierung mindestens eine Diagnosemeldung ausgeben muss", sodass das Kompilieren des Programms mit einer Warnung zulässig ist. Wie Andrew sagte, können Sie mit -Werror = Narrowing einen Fehler machen, wenn Sie möchten.

G ++ 4.6 gab einen Fehler aus, wurde jedoch absichtlich in eine Warnung für 4.7 geändert, da viele Leute (ich selbst eingeschlossen) feststellten, dass das Eingrenzen von Konvertierungen eines der am häufigsten auftretenden Probleme beim Kompilieren großer C ++ 03-Codebasen als C ++ 11 war . Zuvor wohlgeformter Code wie char c [] = {i, 0}; (wo ich immer nur im Bereich von char sein werde) verursachte Fehler und musste in char c [] = {(char) i, 0} geändert werden.

Shafik Yaghmour
quelle
1

Es sieht so aus, als ob GCC-4.7 keine Fehler mehr beim Eingrenzen von Conversions gibt, sondern stattdessen Warnungen.

Kyku
quelle