Can anyone please explain to me, why the compiler allows initialize variables of built-in type if the initializer might lead to the loss of information?
For example C++ Primer, the 5th edition says, that The compiler will not let us list initialize variables of built-in type if the initializer might lead to the loss of information.
but my compiler gcc v 4.7.1 initialized variable a in the following code successfully:
long double ld = 3.1415926536;
int a{ld};
there was just warning: narrowing conversion of ‘ld’ from ‘long double’ to ‘int’ inside { } [-Wnarrowing].
One of the features of initializer lists is that narrowing conversions are not allowed. But the language definition doesn't distinguish between warnings and errors; when code is ill-formed it requires "a diagnostic", which is defined as any message from a set of implementation-defined messages. Warnings satisfy this requirements. That's the mechanism for non-standard extensions: having issued a warning, the compiler is free to do anything it wants to, including compiling something according to implementation-specific rules.
You can set the compiler flag to flag all warnings as error. In that case only it will stop you from doing like that. Otherwise it will only be a warning.
This issue has been coming up lately. With gcc-4.7 a command line switch turns on the required behaviour:
g++ -Werror=narrowing ...
Related
This code works fine:
double a =2.12345;
int b{a}; // According to primer error: narrowing conversion required
int c(a); //This is fine
Is it something which I am missing? For me when a float/double is assigned to int the values on left of the decimal are printed (floor value). Primer says error.
Is it something which I am missing?
The unfortunate detail of compilers deviating from the standard. GCC doesn't enforce it unless you tell it that it should. Try compiling with the -pedantic-errors option.
The primer isn't wrong, it's an ill-formed program according to the C++ standard alone. But compilers may choose to accept it as an extension, such is what GCC does.
Some compilers (rightfully) enforce it by default. MacOSX clang for example would return an error:
type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
For GCC, the option -Wconversion should generate a warning.
In the following code:
#include <iostream>
int main()
{
const long l = 4294967296;
int i = l;
return i; //just to silence the compiler
}
the compiler warns about implicit conversion (using -Wall and -std=c++14) as following:
warning: implicit conversion from 'const long' to 'int' changes value from 4294967296 to 0 [-Wconstant-conversion]
which is ok. But there is no warning if the conversion is from double to int, as in the following code:
#include <iostream>
int main()
{
const double d = 4294967296.0;
int i = d;
return i; //just to silence the compiler
}
Why the compiler reacts differently in these situations?
Note 1: clang version is 3.6.2-svn240577-1~exp1
Note 2: I've tested it with many others versions of gcc, clang and icc thanks to Compiler Explorer (gcc.godbolt.org). So all tested versions of gcc (with exception of 5.x) and icc threw the warning. No clang version did it.
The conversion from double to an integer type changes the value "by design" (think to 3.141592654 converted to an int).
The conversion from long int to int instead may or work or may be undefined behavior depending on the platform and on the value (the only guarantee is that an int is not bigger than a long int, but they may be the same size).
In other words the problems in the conversions between integer types are incidental artifacts of the implementation, not by-design decisions. Warning about them is better especially if it can be detected at compile time that something doesn't work because of those limitations.
Note also that even conversion from double to int is legal and well defined (if done within boundaries) and an implementation is not required to warn about it even when the loss of precision can be seen at compile time. Compilers that warn too much even when the use could be meaningful can be a problem (you just disable warnings or even worse get the habit of accepting a non-clean build as normal).
These implicit conversion rules may add up with other C++ wrinkles getting to truly odd-looking and hard to justify behaviors like:
std::string s;
s = 3.141592654; // No warnings, no errors (last time I checked)
Don't try to use too much logic with C++. Reading specs works better.
Well, by reading this great article named "What Every C Programmer Should Know About Undefined Behavior", specially part #3/3, at LLVM Project Blog, written by Chris Lattner - the main author of LLVM - I could understand better the Clang's Approach to Handling Undefined Behavior.
So, in order to guarantee your strong appeal for optimization and time economy - "ultimate performance" -
Keep in mind though that the compiler is limited by not having dynamic
information and by being limited to what it can without burning lots
of compile time.
Clang doesn't run all related undefined behavior checks by default,
Clang generates warnings for many classes of undefined behavior
(including dereference of null, oversized shifts, etc) that are
obvious in the code to catch some common mistakes.
instead of this, Clang and LLVM provides tools like Clang Static Analyzer, Klee project, and the -fcatch-undefined-behavior (now UndefinedBehaviorSanitizer - UBSan - ) to avoid these possible bugs.
By running UBSan in the presented code, clang++ with the following argument -fsanitize=undefined the bug will be catched as following:
runtime error: value 4.29497e+09 is outside the range of representable values of type 'int'
Considering the following piece of code:
#include <iostream>
auto main() -> int {
double x(7.0);
int i{x};
std::cout << "i = " << x << std::endl;
return 0;
}
When compiled in GCC4.9 it compiles fine with only a warning:
warning: narrowing conversion of ‘x’ from ‘double’ to ‘int’ inside { }
Compiling with either Clang3.3 or VC++2013 gives a compile error:
error: type 'double' cannot be narrowed to 'int' in initializer list
error C2397: conversion from 'double' to 'int' requires a narrowing
Questions:
Which of the compilers is right according to the standard?
Is there any reason why the compilers mentioned above should exhibit such diverse behaviour?
The answer
Both compilers are correct!
Explanation
The Standard doesn't distinguish between an error and a warning, both go under the category of Diagnostics.
1.3.6 diagnostic message [defns.diagnostic]
message belonging to an implementation-defined subset of the implementation's output messages
Since the Standard says that a diagnostic is required in case a program is ill-formed, such as when a narrowing-conversion takes place inside a braced-initializer, both compilers are confirming.
Even if the program is ill-formed from the Standards point of view, it doesn't mandate that a compiler halts compilation because of that; an implementation is free to do whatever it wants, as long as it issues a diagnostic.
The reason for gcc's behavior?
Helpful information was provided by #Jonathan Wakely through comments on this post, below are a merge of the two comments;
he exact reason is that GCC made it an error at one point and it broke ALL THE PROGRAMS so it got turned into a warning instead. Several people who turned on the -std=c++0x option for large C++03 codebases found harmless narrowing conversions to cause most of the porting work to go to C++11See e.g. PR 50810 where Alisdair reports narrowing errors were >95% of the problems in Bloomberg's code base.In that same PR you can see that unfortunately it wasn't a case of "let's just issue a warning and be done with it" because it took a lot of fiddling to get the right behaviour.
This similar ill-fated question
got comments and short answers, before it was closed, to the effect: Because that's
how the language is defined. Here I am asking for the evidence within the
C++ Standard that it is so defined.
gcc 4.8.1 and clang 3.3 alike, with default diagnostic options or stricter,
give errors for extra qualification or explicit qualification on
code such as:
struct x
{
int x::i; // Error: gcc/clang: "extra"
};
int ::y; // Error: gcc: "explicit", clang: "extra"
gcc has diagnosed such errors since v4.1. But popular compilers are not
unanimous about these errors. MSVC++ 2012 (Nov CTP)
gives an error at int ::y; but even with /Wall, gives no diagnostic at all
int x::i; - the kind of case that the ill-fated questioner was raising -
and that difference suggests deliberation by the MS compiler writers.
How are these errors warranted by the Standard, if they are? References to the C++11
Standard will suffice.
An answer might be "They follow from grammar". In that case,
please try to show how they follow from the grammar and feel free to use
the Standard's grammatical classifications. I have a copy and will re-read it
to understand the explanation.
A qualified name in C++ always must refer to a previously declared name. This is specified in clause 8.3 and 3.4.3.2.
You cannot firstly declare a variable or member by using a qualified name - it will end up in a "cannot resolve identifier"-liky compiler error. Such qualifiers are designed to be used for redeclaration. Hence the requirement that these names must find previously declared entities.
It is a bug in the Microsoft compiler to allow x::i within the definition of struct x. There are several of these errors in the MSVC front end, and have been reported to Microsoft but they get closed without being fixed (see similar but different error reported here: https://connect.microsoft.com/VisualStudio/feedback/details/783433/c-compiler-accepts-explicit-constructor-call#details and https://connect.microsoft.com/VisualStudio/feedback/details/794504/keyword-struct-before-constructor-name).
The reason it is invalid is because you are both trying to declare a variable int i and provide a scope using x::i. The scope of the variable is dictated by where it is declared, so trying to declare something with a scope specification is trying to declare it somewhere else, which is invalid.
I have the following code:
typedef void VOID;
int f(void);
int g(VOID);
which compiles just fine in C (using gcc 4.3.2 on Fedora 10). The same code compiled as C++ gives me the following error:
void.c:3: error: ‘<anonymous>’ has incomplete type
void.c:3: error: invalid use of ‘VOID’
Now, this is something in external library and I would like the owner to fix that problem. So I have a question - does C++ standard forbids this construct? Could you give me a pointer/citation? The only thing I can recall is that function declaration with (void) to signal empty parameter list is deprecated in C++, but I don't understand why typedefed VOID does not work.
Yes, as far as i know the second declaration is invalid in C++ and C89, but it is valid in C99.
From The C99 draft, TC2 (6.7.5.3/10):
The special case of an unnamed parameter of type void as the only item in the list
specifies that the function has no parameters.
It's explicitly talking about the type "void", not the keyword.
From The C++ Standard, 8.3.5/2:
If the parameter-declaration-clause is empty, the function takes no arguments. The parameter list (void) is equivalent to the empty parameter list.
That it means the actual keyword with "void", and not the general type "void" can also be seen from one of the cases where template argument deduction fails (14.8.2/2):
Attempting to create a function type in which a parameter has a type of void.
It's put clear by others, notable in one core language issue report here and some GCC bugreports linked to by other answers.
To recap, your GCC is right but earlier GCC versions were wrong. Thus that code might have been successfully compiled with it earlier. You should fix your code, so that it uses "void" for both functions, then it will compile also with other compilers (comeau also rejects the second declaration with that "VOID").
gcc bugs. Edit: since it wasn't clear enough, what I meant was gcc 4.3.2 was compiling it due to bugs. See #32364 and #9278.
I just put your code in a .cpp file, and it compiled with no problems in VS2005, SUSE, Redhat, and Solaris, so I guess your specific gcc version does not approve of this.
Gal