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.
Related
#include <stdio.h>
enum class TEST_ENUM{
VALUE =1,
};
int main( ){
// Gcc will warn.
printf("%u", TEST_ENUM::VALUE);
// Both clang and gcc are happy.
printf("%u", uint32_T(TEST_ENUM::VALUE));
}
Godbolt link
In the above example, gcc will emit the following diagnostic:
<source>:8:12: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'TEST_ENUM' [-Wformat=]
printf("%u", TEST_ENUM::VALUE);
Regardless of compiler version or warnings enabled, I cannot seem to get clang to emit that same diagnostic. Since this is a warnning and not an error, I assume both are standards compliant. Why does gcc complain when clang won't? Is gcc being overly cautious here, or is there actually something worth warning about?
From cppreference:
... - arguments specifying data to print. If any argument after default conversions is not the type expected by the corresponding conversion specifier, or if there are fewer arguments than required by format, the behavior is undefined. If there are more arguments than required by format, the extraneous arguments are evaluated and ignored
Is gcc being overly cautious here, or is there actually something worth warning about?
Not at all overly cautios. You are passing a parameter of wrong type!
Since this is a warnning and not an error, I assume both are standards compliant. Why does gcc complain when clang won't?
When your code has undefined behavior then compilers are not required to issue any diagnostics. GCC is just being nice to you here.
If you are forced to use a type-unsafe API you can always wrap it into something type-safe :
void Log(const TEST_ENUM& x) {
the_actual_logging_api( "%u", static_cast<std:underlying_type<TEST_ENUM>>(x));
}
In order to prevent "unexpected issues", with format specifies where types are defined in other modules, I'm looking for a cast operator which will fail to compile on a narrowing conversion: this represents a fundamental type error that needs to be addressed.
For example, a using in some external header that has recently been changed from a compatible type to an incompatible type:
namespace y {
using X = uint64_t; // "recent change", was previously int32_t
}
The goal is to get this to fail (error not warning-as-error), as the result is used as "%d.
y::X a; // question covers ANY value in domain of X, not if overflow at run-time
int32_t r = cast_that_fails_to_compile_if_can_narrow<int32_t>(a);
// So this is guaranteed to always be a valid type specifier
// (per http://www.cplusplus.com/reference/cstdio/printf/)
// for the provided argument if the cast/code above compiles.
printf("%d", r);
(In this case the intractable narrowing issue should be handled by additional code changes.)
Initialization with braces (but not parentheses) disallows narrowing conversions:
int32_t r{a};
// or
int32_t r = {a};
// or
auto r = int32_t{a};
Your compiler may be allowing this anyway, but that is not standard-conform [1]. E.g. for GCC you need to add the -pedantic-errors flag for it to actually generate a hard error.
Also note that the type for %d should be int. If you use int32_t instead, you are risking a similar issue should a platform use a differently sized int.
You can use it directly in the printf call:
printf("%d", int{a});
[1] The standard always only requires compilers to print some diagnostic. It does not require hard errors preventing the compilation of the program. GCC for example only warns by-default, but that is still conforming.
A template function can be made to check for narrowing casts but permit other static casts:
template <typename Tto, typename Tfr>
Tto static_cast_prohibit_narrow(Tfr v)
{
static_assert(sizeof(Tfr) <= sizeof(Tto), "Narrowing cast prohibited");
return static_cast<Tto>(v);
}
The compiler produces an error encountering a narrow cast while permitting other static_casts.
int main()
{
long long int i1{ 4 };
int i2{ 5 };
//i2 = static_cast_prohibit_narrow<int>(i1); // Compiler static_assert error
i1 = static_cast_prohibit_narrow<int>(i2);
}
constexpr int i = 100;
struct F { F(unsigned int){} };
int main() { F{i}; }
The snippet above:
Compiles with no warnings on g++ 7 with -Wall -Wextra -Wpedantic.
Compiles with no warnings on clang++ 4 with -Wall -Wextra -Wpedantic.
Fails to compile on MSVC 2017:
conversion from 'const int' to 'unsigned int' requires a narrowing conversion
Q: is MSVC wrong here?
live example on godbolt.org
int i = 100;
struct F { F(unsigned int){} };
int main() { F{i}; }
Compiles with warnings on g++ 7 with -Wall -Wextra -Wpedantic:
narrowing conversion of 'i' from 'int' to 'unsigned int'
Fails to compile clang++ 4 with -Wall -Wextra -Wpedantic:
non-constant-expression cannot be narrowed from type 'int' to 'unsigned int' in initializer list
Fails to compile on MSVC 2017:
conversion from 'const int' to 'unsigned int' requires a narrowing conversion
Q: is g++ wrong here? (i.e. should it produce an hard error?)
live example on godbolt.org
There is never a requirement that any C++ program produce a hard error. There are requirements to print diagnostics. The form of the diagnostic is unspecified by the standard: an old joke is that printing out a single space satisifies the diagnostic requirements of the standard. That would be a quality of implementation issue.
There are ill-formed programs upon which the standard places no restrictions on their behavior, and sometimes a mandatory diagnostic.
There are cases where a program is ill-formed and a diagnostic is required. One way to handle that is to produce a message saying it is an error, then do not generate any binary to run. Another way is to produce a message saying it is a warning, then produce a binary that can be run.
So, g++ is not wrong under the standard for merely printing out a warning.
The resulting program is technically all undefined behavior; g++ could format your hard drive when it runs without violating the standard. That would be considered a quality of implementation issue.
Shafik's answer here covers the first question. i is constant expression and its value fits the target type; there should be no warning or error about the narrowing conversion.
The C++ standard does not defend you against hostile compilers.
Reportedly, -pedantic-errors can be passed to g++ to have it generate hard errors instead of warnings when the standard mandates the resulting program would be ill-formed.
I have the following code:
class A
{
public:
A(const unsigned int val) : value(val) {}
unsigned int value;
};
int main()
{
int val = 42;
A a(val);
A b{val}; // <--- Warning in GCC, error in Microsoft Visual Studio 2015
return 0;
}
Why does the narrowing conversion warning appear only in case of list initialization usage?
list initialization was introduced since C++11 with the feature prohibiting implicit narrowing conversions among built-in types. At the same time, the other two "old-style" (since C++98) initialization forms which use parentheses and equal-sign like
int val = 42;
A a(val);
A a = val;
don't change their behavior to accord with list initialization, because that could break plenty of legacy code bases.
Under the standard, narrowing conversions are illegal in that context. They are legal in the other context. (By "illegal", I mean make the program ill-formed).
The standard requires that a compiler issue a diagnostic in that particular case (of making the program ill-formed). What the compiler does after emitting the diagnostic the standard leaves undefined.
MSVC chooses to halt compilation. Gcc chooses to emit nasal demons pretend the program makes sense, and do the conversion, and continue to compile.
Both warnings and errors are diagnostics as far as the standard is concerned. Traditionally errors are what you call diagnostics that preceed the compiler stopping compilation.
Also note that compilers are free to emit diagnostics whenever they want.
Traditionally warnings are used when you do something the standard dictates is a well formed program yet the compiler authors consider ill advised, and errors when the standard detects an ill-formed program, but most compilers do not enforce that strictly.
The reason behind the warning is already explained by other answers.
This is how to fix this warning/error. Create a constructor which takes initializer_list as argument.
A(std::initializer_list<int> l) : value(*(l.begin())) {
cout << "constructor taking initializer list called\n";
}
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.