Is printing an enum without a cast okay in C++? - c++

#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));
}

Related

narrowing conversion from 'int' to 'SHORT [duplicate]

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.

What exactly does `(void)SomeBaseClass;` do? [duplicate]

This question already has answers here:
Why cast unused return values to void?
(10 answers)
Closed 1 year ago.
An often used statement like (void)x; allows to suppress warnings about unused variable x. But if I try compiling the following, I get some results I don't quite understand:
int main()
{
int x;
(short)x;
(void)x;
(int)x;
}
Compiling this with g++, I get the following warnings:
$ g++ test.cpp -Wall -Wextra -o test
test.cpp: In function ‘int main()’:
test.cpp:4:13: warning: statement has no effect [-Wunused-value]
(short)x;
^
test.cpp:6:11: warning: statement has no effect [-Wunused-value]
(int)x;
^
So I conclude that casting to void is very different from casting to any other types, be the target type the same as decltype(x) or something different. My guess at possible explanations is:
It is just a convention that (void)x; but not the other casts will suppress warnings. All the statements equally don't have any effect.
This difference is somehow related to the fact that void x; isn't a valid statement while short x; is.
Which of these if any is more correct? If none, then how can the difference in compiler warnings be explained?
Casting to void is used to suppress compiler warnings. The Standard says in §5.2.9/4 says,
Any expression can be explicitly converted to type “cv void.” The
expression value is discarded.
This statement:
(void)x;
Says "Ignore the value of x." There is no such type as void - it is the absence of a type. So it's very different from this:
(int)x;
Which says "Treat x as if it were an integer." When the resulting integer is ignored, you get a warning (if it's enabled).
When you ignore something which is nothing, it is not considered a problem by GCC--and with good reason, since casting to void is an idiomatic way to ignore a variable explicitly in C and C++.
The standard does not mandate generating a warning ("diagnostic" in standardese) for unused local variables or function parameters. Likewise, it does not mandate how such a warning might be suppressed. Casting a variable expression to void to suppress this warning has become an idiom in the C and later C++ community instead because the result cannot be used in any way (other than e.g. (int)x), so it's unlikely that the corresponding code is just missing. E.g.:
(int)x; // maybe you meant f((int)x);
(void)x; // cannot have intended f((void)x);
(void)x; // but remote possibility: f((void*)x);
Personally, I find this convention too obscure still, which is why I prefer to use a function template:
template<typename T>
inline void ignore(const T&) {} // e.g. ignore(x);
The idiomatic way to ignore function parameters is, however, to omit their name (as seen above). A frequent use I have for this function is when I need to be able to name a function parameter in conditionally compiled code such as an assert. I find e.g. the following more legible than the use of #ifdef NDEBUG:
void rate(bool fantastic)
{
assert(fantastic);
ignore(fantastic);
}
Possible use:
auto it = list_.before_begin();
for (auto& entry : list_)
{
(void)entry; //suppress warning
++it;
}
Now the iterator 'it' points to the last element

Narrowing conversion from `int` (constant expression) to `unsigned int` - MSVC vs gcc vs clang

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.

What does casting to `void` really do? [duplicate]

This question already has answers here:
Why cast unused return values to void?
(10 answers)
Closed 1 year ago.
An often used statement like (void)x; allows to suppress warnings about unused variable x. But if I try compiling the following, I get some results I don't quite understand:
int main()
{
int x;
(short)x;
(void)x;
(int)x;
}
Compiling this with g++, I get the following warnings:
$ g++ test.cpp -Wall -Wextra -o test
test.cpp: In function ‘int main()’:
test.cpp:4:13: warning: statement has no effect [-Wunused-value]
(short)x;
^
test.cpp:6:11: warning: statement has no effect [-Wunused-value]
(int)x;
^
So I conclude that casting to void is very different from casting to any other types, be the target type the same as decltype(x) or something different. My guess at possible explanations is:
It is just a convention that (void)x; but not the other casts will suppress warnings. All the statements equally don't have any effect.
This difference is somehow related to the fact that void x; isn't a valid statement while short x; is.
Which of these if any is more correct? If none, then how can the difference in compiler warnings be explained?
Casting to void is used to suppress compiler warnings. The Standard says in §5.2.9/4 says,
Any expression can be explicitly converted to type “cv void.” The
expression value is discarded.
This statement:
(void)x;
Says "Ignore the value of x." There is no such type as void - it is the absence of a type. So it's very different from this:
(int)x;
Which says "Treat x as if it were an integer." When the resulting integer is ignored, you get a warning (if it's enabled).
When you ignore something which is nothing, it is not considered a problem by GCC--and with good reason, since casting to void is an idiomatic way to ignore a variable explicitly in C and C++.
The standard does not mandate generating a warning ("diagnostic" in standardese) for unused local variables or function parameters. Likewise, it does not mandate how such a warning might be suppressed. Casting a variable expression to void to suppress this warning has become an idiom in the C and later C++ community instead because the result cannot be used in any way (other than e.g. (int)x), so it's unlikely that the corresponding code is just missing. E.g.:
(int)x; // maybe you meant f((int)x);
(void)x; // cannot have intended f((void)x);
(void)x; // but remote possibility: f((void*)x);
Personally, I find this convention too obscure still, which is why I prefer to use a function template:
template<typename T>
inline void ignore(const T&) {} // e.g. ignore(x);
The idiomatic way to ignore function parameters is, however, to omit their name (as seen above). A frequent use I have for this function is when I need to be able to name a function parameter in conditionally compiled code such as an assert. I find e.g. the following more legible than the use of #ifdef NDEBUG:
void rate(bool fantastic)
{
assert(fantastic);
ignore(fantastic);
}
Possible use:
auto it = list_.before_begin();
for (auto& entry : list_)
{
(void)entry; //suppress warning
++it;
}
Now the iterator 'it' points to the last element

Why doesn't narrowing conversion used with curly-brace-delimited initializer cause an error?

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.