Skip to main content

C++ Initialization Subtleties (significance of -Werror compiler option)

Modern C++ continuous integration build pipelines might produce huge logs which may be easily overlooked. Among other errors/warnings, a potential risk caused by invalid narrowing assignments might be lurking in those dark corners... This write up is a little reminder about an essential feature in modern C++ compilers and can help defend against that specific problem.

Prior to C++11, the following was a valid assignment and still is even in C++20 or higher ...

unsigned int unsignedSinceBirth = 10;
unsignedSinceBirth = -10;  // assigning a negative value to an unsigned container

printUnsignedVal(unsignedSinceBirth);

which when compiled using these options "g++ -std=c++20 <cpp files> -o <executable-name>" does not even emit a warning. And an executable is generated successfully.

The output of running that code is an arbitrary unexpected value.

Modern C++ (post Cpp11) allow uniform initialization syntax which can help the compiler detect this situation as follows:

unsigned int unsignedSinceBirth = 10;
unsignedSinceBirth = {-10};  // assigning a negative value to an unsigned container
printUnsignedVal(unsignedSinceBirth);

Compiling this revised code fails and that's a good thing. G++ emits this error:

error: narrowing conversion of ‘-10’ from ‘int’ to ‘unsigned int’ [-Wnarrowing]

But when the assignment happens in the following manner, compiler only emits a warning and the compilation still succeeds:

unsigned int unsignedSinceBirth = 10;

// .. somewhere else in the code

int rougeInteger = -10;
unsignedSinceBirth = {rougeInteger}; 
printUnsignedVal(unsignedSinceBirth);

Compiling the revised code does not fail and that's a bad thing. G++ only emits a warning:

warning: narrowing conversion of ‘rougeUnsignedNum’ from ‘int’ to ‘unsigned int’ [-Wnarrowing]

In absence of eagle eyes, that warning will most likely go unnoticed in large build logs and the compilation will succeed. It turns out there is a way to enforce the compiler detect such assignments.

If we include "-Werror" option in the G++ command line as follows:

 "g++ -std=c++20 -Werror <cpp files> -o <executable-name>"

The complier will emit an error and compilation will not be successful and that's the effect we were looking for.

Crux: Always pay attention to what compiler is trying to tell us. If it's not telling, force it to say something :)

Note: Compilation using LLVM clang as follows complains with an error without needing an explicit option:

 "clang -std=c++20 <cpp files> -o <executable-name>"

with the following error:

error: non-constant-expression cannot be narrowed from type 'int' to 'unsigned int' in initializer list [-Wc++11-narrowing] 

Comments