I learnt about curly-brace-delimited initializer in The C++ Programming Language, 4th ed. > Chapter 2: A Tour of C++: The Basics.
I am quoting from the book below.
The = form is traditional and dates back to C, but if in doubt, use the general {} -list form (§6.3.5.2).
If nothing else, it saves you from conversions that lose information (narrowing conversions; §10.5):
int i1 = 7.2; // i1 becomes 7
int i2 {7.2}; // error : floating-point to integer conversion
int i3 = {7.2}; // error : floating-point to integer conversion (the = is redundant)
However, I am unable to reproduce these results.
I have the following code.
#include <iostream>
int main()
{
int i1 = 7.2;
int i2 {7.2};
int i3 = {7.2};
std::cout << i1 << "\n";
std::cout << i2 << "\n";
std::cout << i3 << "\n";
}
When I compile and run it, I don't get any error. I get a warning about std=c++11
but no error.
$ g++ init.cpp
init.cpp: In function ‘int main()’:
init.cpp:6:12: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11
int i2 {7.2};
^
$ ./a.out
7
7
7
Further, the warning is only for the second assignment but there is no warning for the third assignment. This seems to indicate that the =
is not really redundant as mentioned in the book. If =
were redundant, either both the second and third assignments would have produced warnings or both would not have produced warnings.
Then I compile them with the -std=c++11
flag.
$ g++ -std=c++11 init.cpp
init.cpp: In function ‘int main()’:
init.cpp:6:16: warning: narrowing conversion of ‘7.2000000000000002e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
int i2 {7.2};
^
init.cpp:7:18: warning: narrowing conversion of ‘7.2000000000000002e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
int i3 = {7.2};
^
$ ./a.out
7
7
7
Still no error. Only warnings. Although in this case the second and third assignments behave identically with respect to generating warnings.
So my question is: Although the book mentions that the second and third assignments are errors, why doesn't this code fail to compile?
This is ill-formed and there should be diagnostic, however it can either be a warning(which you received) or an error. gcc made this a warning for several versions due to porting issue from C++03:
The standard only requires that "a conforming implementation shall issue at least one diagnostic message" so compiling the program with a warning is allowed. As Andrew said, -Werror=narrowing allows you to make it an error if you want.
G++ 4.6 gave an error but it was changed to a warning intentionally for 4.7 because many people (myself included) found that narrowing conversions where one of the most commonly encountered problems when trying to compile large C++03 codebases as C++11. Previously well-formed code such as char c[] = { i, 0 }; (where i will only ever be within the range of char) caused errors and had to be changed to char c[] = { (char)i, 0 }
but now recent versions of gcc and clang make this an error, see it live for gcc.
For reference the draft C++11 standard section 8.5.4
[dcl.init.list] says:
Otherwise, if the initializer list has a single element, the object or
reference is initialized from that element; if a narrowing conversion
(see below) is required to convert the element to T, the program is
ill-formed. [ Example:
int x1 {2}; // OK
int x2 {2.0}; // error: narrowing
—end example ]
and:
A narrowing conversion is an implicit conversion
- from a floating-point type to an integer type, or
[...]
[ Note: As indicated above, such conversions are not allowed at the top level in list-initializations.—end
note ] [ Example:
[...]
int ii = {2.0}; // error: narrows
[...]
So a floating point to integer conversion is a narrowing conversion and is ill-formed.
and section 1.4
Implementation compliance [intro.compliance] says:
Although this International Standard states only requirements on C++ implementations, those requirements
are often easier to understand if they are phrased as requirements on programs, parts of programs, or
execution of programs. Such requirements have the following meaning:
[...]
- If a program contains a violation of any diagnosable rule or an occurrence of a construct described in
this Standard as “conditionally-supported” when the implementation does not support that construct,
a conforming implementation shall issue at least one diagnostic message.
[...]
Tells us that only a diagnostic is required.
C++ language does not distinguish "warnings" from "errors". C++ only has diagnostic messages. The warnings you received are diagnostic messages. The language specification does not require compilers to stop compilation when they encounter erroneous (aka ill-formed) code. All compilers have to do is issue a diagnostic message, and then they can continue compiling, if they so desire.
This means that in general case it is your responsibility to tell warnings that are "just warnings" from warnings that actually indicate genuine errors, especially with such permissive compilers as GCC.
This also means that the actual real-life behavior is a matter of your compiler setup. Ask your compiler to be more restrictive in that regard, if possible. In GCC you might try -pedantic-errors
switch for that purpose.
P.S. In my experiments with GCC, -std=c++11
is sufficient to make it generate errors for your code. If you are getting warnings instead, it could be a matter of compiler version.