C ++ 0xでの変換の絞り込み。それは私だけですか、それともこれは壊滅的な変化のように聞こえますか?


86

C ++ 0xは、aから。のいわゆるナローイング変換を必要とするため、次のコードおよび同様のコードを不正な形式doubleにしintます。

int a[] = { 1.0 };

この種の初期化が実際のコードで多く使用されているかどうか疑問に思います。この変更によっていくつのコードが壊れますか?コードがまったく影響を受けている場合、コードでこれを修正するのは大変な努力ですか?


参考までに、n3225の8.5.4 / 6を参照してください。

ナローイング変換は暗黙的な変換です

  • 浮動小数点型から整数型へ、または
  • long doubleからdoubleまたはfloat、またはdoubleからfloat。ただし、ソースが定数式であり、変換後の実際の値が表現可能な値の範囲内にある場合(正確に表現できない場合でも)、または
  • ソースが定数式であり、変換後の実際の値がターゲットタイプに適合し、元のタイプに変換されたときに元の値を生成する場合を除いて、整数型またはスコープなしの列挙型から浮動小数点型へ、または
  • 整数型またはスコープなしの列挙型から、元の型のすべての値を表すことができない整数型へ。ただし、ソースが定数式であり、変換後の実際の値がターゲット型に適合し、次の場合に元の値を生成する場合を除きます。元のタイプに変換されます。

1
これが組み込み型の初期化にのみ有効であると仮定すると、これがどのように害を及ぼすかはわかりません。確かに、これはいくつかのコードを壊すかもしれません。しかし、簡単に修正できるはずです。
Johan Kotlinski 2010

1
@John Dibling:いいえ、値がターゲットタイプで正確に表現できる場合、初期化は不正な形式ではありません。(そして0intとにかくすでにです。)
aschepler 2010

2
@Nim:これは{中括弧の初期化子内でのみ不正な形式}であり、これらの従来の使用法は配列とPOD構造体のみであることに注意してください。また、既存のコードにそれらが属する場所に明示的なキャストがある場合、それは壊れません。
aschepler 2010

4
ワーキングペーパーにあるように、@ j_random_hackerint a = 1.0;はまだ有効です。
Johannes Schaub-litb 2010

1
@litb:ありがとう。実際、私はそれを理解できますががっかりしていると思います-私見では、C ++の最初からすべてのナローイング変換に明示的な構文を要求する方がはるかに良かったでしょう。
j_random_hacker 2010

回答:


41

GCCを使用したときに、この重大な変更に遭遇しました。コンパイラは、次のようなコードのエラーを出力しました。

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

機能中void foo(const long long unsigned int&)

エラー:の狭小変換(((long long unsigned int)i) & 4294967295ull)からlong long unsigned intunsigned int内部{}

エラー:の狭小変換(((long long unsigned int)i) >> 32)からlong long unsigned intunsigned int内部{}

幸い、エラーメッセージは簡単で、修正は簡単でした。

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

コードは外部ライブラリにあり、1つのファイルに2回しか出現しませんでした。重大な変更が多くの​​コードに影響を与えるとは思わない。ただし初心者は混乱する可能性 あります。


9

過去12年間に書いたC ++コードのいずれかにこの種の問題があることを知って、私は驚き、失望しました。しかし、私が何かを見逃していない限り、ほとんどのコンパイラーは、コンパイル時の「ナローイング」について警告を発していました。

これらもコンバージョンを狭めていますか?

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

もしそうなら、それらはあなたの浮動型から整数型の例よりも少し頻繁に現れるかもしれないと思います。


1
これがコードで見つかるのは珍しいことではないとあなたが言う理由がわかりません。USHRT_MAXの代わりに-1またはINT_MAXを使用する間のロジックは何ですか?USHRT_MAXは2010年後半に限界に達していませんでしたか?

8

私が遭遇した実際的な例:

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

数値リテラルは暗黙的doubleに昇格を引き起こします。


1
だからfloat、書くことによってそれを作り0.5fます。;)
underscore_d

2
@underscore_dがfloattypedefまたはテンプレートパラメーターの場合は機能しません(少なくとも精度を失うことなく)が、重要なのは、記述されたコードが正しいセマンティクスで機能し、C ++ 11でエラーになったということです。つまり、「重大な変化」の定義です。
Jed

7

誰かが次のようなものに巻き込まれたとしても、私はそれほど驚かないでしょう。

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

(私の実装では、最後の2つは、int / longに変換して戻したときに同じ結果を生成しないため、狭くなっています)

でも、これを書いたことは覚えていません。限界への近似が何かに役立つ場合にのみ役立ちます。

これも少なくとも漠然ともっともらしいようです:

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
}

しかし、それは完全に説得力があるわけではありません。なぜなら、私が正確に2つの値を持っていることがわかっているのなら、なぜそれらを単にではなく配列に入れるのfloat floatval1 = val1, floatval1 = val2;でしょうか。しかし、なぜそれをコンパイルする必要があるのか​​(そして、精度の低下がプログラムの許容可能な精度の範囲内でfloat asfloat[] = {val1, val2};あれば機能するのか)、そうではないのはなぜですか?いずれにせよ、2つのintから2つのfloatを初期化していますが、1つのケースでは、2つのfloatがたまたま集合体のメンバーであるということです。

これは、(特定の実装では)ソースタイプのすべての値がデスティネーションタイプで表現可能であり、元の値に変換可能であるにもかかわらず、非定数式が結果として変換を狭める場合には特に厳しいように思われます。

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

バグがないと仮定すると、おそらく修正は常に変換を明示的にすることです。マクロで奇妙なことをしているのでない限り、配列初期化子は配列の型の近く、または少なくとも型を表すものの近くにしか表示されないと思います。これはテンプレートパラメータに依存する可能性があります。したがって、冗長であれば、キャストは簡単なはずです。


9
「正確に2つの値があることがわかっている場合、なぜそれらを配列に入れるのか」 -たとえば、OpenGLのようなAPIがそれを必要とするため。
Georg Fritzsche 2010

5

CFLAGSに-Wno-narrowingを追加してみてください。例:

CFLAGS += -std=c++0x -Wno-narrowing

またはC ++コンパイラの場合はCPPFLAGS(もちろん、それはあなたのビルドシステムやあなたのMakefileに依存)
Mikolasan

4

変換エラーを狭めることは、暗黙の整数プロモーションルールとうまく相互作用しません。

次のようなコードでエラーが発生しました

struct char_t {
    char a;
}

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

これにより、ナローイング変換エラーが発生します(これは標準に従って正しいです)。その理由はcd暗黙的にに昇格さintれ、結果intがイニシャライザリストのcharに絞り戻されないためです。

OTOH

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

もちろんまだ大丈夫です(そうでなければ、すべての地獄が解き放たれます)。しかし、驚くべきことに、

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

cとdの合計がCHAR_MAX未満の場合、問題はなく、警告なしでコンパイルされます。これはまだC ++ 11の欠陥だと思いますが、そこにいる人々はそうではないと考えています-おそらく、暗黙の整数変換(人々がコードを書いたときの過去の遺物)を取り除くことなしに修正するのは簡単ではないためです以下のようにchar a=b*c/dしても、(B * C)> CHAR_MAX)場合は、おそらく良いことである変換誤差を()狭く仕事にそれを期待しました。


私は本当に迷惑なナンセンスである次のことに遭遇しました:unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m };<-{}内の変換を狭める。本当に?したがって、operator&は暗黙的にunsigned charsをintに変換しますか?まあ、私は気にしません、結果はまだ符号なしの文字、arghであることが保証されています。
カルロウッド

"暗黙の整数変換」プロモーション?
curiousguy 2018

2

この機能の実際の経験から、C ++ 03コードベースをC ++ 11に移植する際の実際の苦痛により、多くの場合、gccがエラーからの警告に狭まったことが示されているため、これは重大な変更でした。gccバグレポートでこのコメントを参照してください:

この規格では、「準拠する実装は少なくとも1つの診断メッセージを発行する」ことのみを要求しているため、警告付きでプログラムをコンパイルできます。Andrewが言ったように、-Werror = narrowingを使用すると、必要に応じてエラーにすることができます。

G ++ 4.6でエラーが発生しましたが、4.7の警告に意図的に変更されました。これは、多くの人(私自身を含む)が、最も一般的に発生する変換の1つである変換を絞り込むことを発見したためです。大きなC ++ 03コードベースをC ++ 11としてコンパイルしようとしたときに問題の。char c [] = {i、0};などの以前は整形式のコード (iはcharの範囲内にのみ存在します)エラーが発生し、char c [] = {(char)i、0}に変更する必要がありました。


1

GCC-4.7は、変換を絞り込むためのエラーを表示しなくなりましたが、代わりに警告を表示します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.