Narrowing conversion error using newer compiler

Viewed 1207

I'm trying to compile my code on a new system, and I'm suddenly running into trouble with one of my older libraries. This is an example snippet of the code that is causing the issue:

int main() {
    static const unsigned char pad_block[8] = {
    '\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};
}

I'm compiling it using g++ -o test main.cpp. My old system uses g++ 4.8.5, and there it compiles without issue - not even a warning. On the newer system with g++ 7.5.0, the following error appears:

main.cpp: In function ‘int main()’:
main.cpp:3:67: error: narrowing conversion of ‘'\37777777600'’ from ‘char’ to ‘unsigned char’ inside { } [-Wnarrowing]
     '\x80', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};

I understand the issue of narrowing conversions in general, but this specific case I don't understand. My questions:

  • Why does this issue suddenly appear, without specifying a new C++ version?
  • Where does the number '\37777777600' come from?
  • I'd prefer not to change the library - is there another way around this?
2 Answers

Why does this issue suddenly appear, without specifying a new C++ version?

Your issue is essentially the same thing as:

void foo(char x) {
    unsigned char y = {x};
}

This syntax, introduced in C++11, applies stricter rules to implicit conversions. However, that specific C++11 features retroactively changes how braced-initialization of arrays work by applying these stricter rules to them as well. This is mostly because they look similar and it'd be confusing otherwise.

Because breaking changes were introduced, from GCC 5.0 onwards, g++ has started reporting these C++11 failures as warnings in C++03 code in order to ease transition. You can find more details in the following answer: https://stackoverflow.com/a/28466553/4442671

Your other two questions are well covered by the other answer, but a bit can be added:

Where does the number '\37777777600' come from?

On top of what @AdrianMole said, it's worth mentioning that this conversion from '\0x80' to '\37777777600' appears to be entirely internal to gcc's reporting process, as evidenced by the fact that the following assertion passes. It requires -std=c++11, but your code produces the exact same error with it, so it's fair to say that it applies equivalently.

static_assert(std::is_same<char, decltype('\x80')>::value, "");

I'd prefer not to change the library - is there another way around this?

If the library is compiled in isolation, adding -Wno-narrowing to its compiler options will make the error go away without touching the code, but this does run the risk of hiding more severe violations if/when you update the library code. Changing the char literals to int literals is a better fix all-around unless you need to do a lot of those and maintaining the patch would be a pain. Obviously, if the library has no upstream source, then there is no reason not to go down the refactoring road here.

Why does this issue suddenly appear, without specifying a new C++ version?

Newer releases/versions of the 'same' compiler quite often have stricter requirements (in terms of conformance to the C++ Standard) than earlier/older ones. This appears to be so in your case.

Where does the number '\37777777600' come from?

This is the value of the '\x80' literal (-128) expressed as a 32-bit integer in octal. The value is sign-extended to an int (32-bits on your system) because of the following rule (quoted from this Draft C11 Standard):

5.13.3 Character literals


2   … A multicharacter literal, or an ordinary character literal containing a single c-char not representable in the execution character set, is conditionally-supported, has type int, and has an implementation-defined value.

It would appear that -128 is not a 'representable character' in your platform's execution set. However, why the g++ compiler chooses to use octal is not something I can answer in any authoritative manner. (Tradition, maybe?)

I'd prefer not to change the library - is there another way around this?

There may be a command-line compiler switch that reduces the compiler's strictness; MSVC has the /permissive flag to turn off "Conformance Mode" but I'm not sure what the g++ equivalent is (or even if there is one). Failing that, a fairly trivial change to your code, to remove the use of character literals for unsigned char data (which are, anyway, expressed as raw hex values) would work:

int main()
{
    static const unsigned char pad_block[8] = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    //...
}
Related