g++4.9 and g++5 different behaviour when narrowing in initializing list
Asked Answered
R

3

4

Consider this code:

#include <iostream>

int main()
{
    int i{10.1}; // narrowing, should not compile
    std::cout << i << std::endl;
}

According to the C++11 standard, it should not compile (narrowing in brace initialization is forbidden.)

Now, compiling with g++4.9.2 -std=c++11 only emits a warning

warning: narrowing conversion of '1.01e+1' from 'double' to 'int' inside { } [-Wnarrowing]

Removing the -std=c++11 flag results in a warning regarding the brace init, but not any narrowing:

warning: extended initializer lists only available with -std=c++11 or -std=gnu++11

On the other hand, g++5 doesn't compile it, provided you compile with g++5 -std=c++11. However, if -std=c++11 is omitted, then even g++5 happily compiles it, giving just a warning related to the brace init, not to the narrowing:

warning: extended initializer lists only available with -std=c++11 or -std=gnu++11

The above behaviour seems buggy, g++4.9 should not compile the code, and it is more than weird that g++5 compiles it if you forget to specify -std=c++11. Is this a known problem?

Romilda answered 11/2, 2015 at 22:48 Comment(3)
@MattMcNabb even if this is the case, then still g++5 should emit at least a warning when compiling without -std=c++11 And the fact that confused me is that even on Stroustroup page, stroustrup.com/C++11FAQ.html#narrowing he talks about "how to prevent narrowing". IMO, just emitting a warning is not preventing anything.Romilda
this is teh same as int foo = 1.0f, it compiles everywhere but will generate a warning if the level is high enough (lvl 4 on msvc)Treacle
@Treacle it is not really the same. One of the "qualities" of brace initialization was this one, preventing narrowing. But it looks like the compiler is just required to emit a warning. On the link above, B. Stroustroup even says: "However, in C++11, {} initialization doesn't narrow: ..."Romilda
D
7

The standard never says anything "should not compile" (except for #error). Certain ill-formed programs must emit a diagnostic and issuing a warning satisfies that.

You can cause gcc to stop compilation on all diagnostics by using the switch -Werror. It can be narrowed to specific warnings, e.g. -Werror=narrowing.

If you are compiling in GNU++ or whatever the default mode is instead of C++11 then the compiler can do whatever it likes, including accepting narrowing conversions without complaint.

Reference: N3936 [intro.compliance]/2

  • If a program contains a violation of any diagnosable rule [...], a conforming implementation shall issue at least one diagnostic message.

  • If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program.

[defns.diagnostic]

diagnostic message

message belonging to an implementation-defined subset of the implementation’s output messages

Note also from the first bullet point that it is not required that the number or content of messages corresponds to the number or content of the violations.

The standard leaves it completely up to the compiler to decide how to organize its errors and/or warnings, with the proviso that for certain violations it can't silently ignore it.

Drawbridge answered 11/2, 2015 at 23:0 Comment(6)
Thanks. The fact that confused me is that if you look in the standard, you see the narrowing declarations marked with an // error comment, same as e.g. int& r; // error. Now, common sense tells me that when you comment something with error, it should not be compilable. I have to admit that I did not know that "ill-formed programs" are just required to emit a diagnostic and not a compile time error.Romilda
This answer would be more complete by stating where in the standard it says this.Jolson
@Romilda It's up to gcc/g++'s developers to decide the default setting for whether a particular condition is reported as "error" or "warning". I presume they try to go with what their userbase would complain about the leastDrawbridge
@MattMcNabb thanks for the effort, I certainly didn't know this.Romilda
Pradhan also makes a good point that the compiler would typically use "warning" for conditions where it has an extension in placeDrawbridge
The C++ standard doesn't even say #error should not compile, unlike C. See open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#745Boltrope
P
9

The reason that the narrowing conversion inside {} are only an error in C++11 mode is simple: it isn't an error in C++03. Now, T var{value}; is new C++11 syntax, but T var = {value}; was already valid C++03 syntax, and did allow narrowing conversions.

int i = { 10.1 }; // valid C++03, invalid C++11

It makes it easier for the GCC developers to treat narrowing conversions the same in T var{value}; and T var={value}; initialisations. This is useful because it avoids two separate code paths for the warning in the compiler.

It makes it easier for the GCC developers to accept even the T var{value}; syntax in C++03 mode, merely warning about it. Several other C++11 syntax extensions are also enabled in C++03 mode. This is useful because several C++11 syntax extensions are used in GCC's implementation of the standard library (where warnings about it are suppressed).

The reason that int i{10.1}; isn't an error in GCC 4.9 in C++11 mode, but was made an error in GCC 5, is because not treating it as an error caused valid code to be rejected. The C++ standard requires treating it as an error in SFINAE contexts, and here is a valid C++11 program that runs incorrectly because of this with GCC 4.9:

#include <stdio.h>
template <typename T> void f(double) { puts("ok"); }
template <typename T, typename = decltype(T{10.1})> void f(int) { puts("error"); }
int main() { f<int>(1); }

This is supposed to print "ok". The second overload is supposed to be discarded.

With GCC 4.9, it prints "error", because the second overload isn't discarded, and int is a better match than double.

Pannell answered 11/2, 2015 at 23:25 Comment(7)
Just to make sure I understand, the calling of f<int>() will become ambiguous in g++49, right, due to conversion being allowed? Whereas in g++5 the second is rejected due to SFINAE. You probably meant f() in the first line.Romilda
@Romilda Yes, GCC 4.9 reports it as ambiguous. I did mean ..., hoping to trick it into preferring the second declaration (which would mean it generates wrong code instead of just rejecting valid code), but forgot that ... doesn't work for that. I'll edit it with a better example, where GCC 4.9 does generate wrong code, when I've got one. :)Pannell
I think I have one example, do you mind if I added it to your answer?Romilda
@Romilda Thanks for the offer. I had just edited one in seconds after your comment. Is yours similar to mine?Pannell
#include <iostream> template <typename T, typename... U> void f(U...) { std::cout << "first" << std::endl; } template < typename T, typename = decltype(T {10.1}) > void f() { std::cout << "second" << std::endl; }; int main() { f<int>(); } So it is similar in flavour. Your remark about generating wrong code is excellent, the problem becomes serious in such situations.Romilda
@Romilda Ah, yeah, I guess that works too, although IIRC that's an area of the standard that has been changed quite a bit after C++11 was published, and where compiler support varies depending on which DR resolutions have been implemented.Pannell
The warning/error classification and SFINAE isn't linked. Newer versions of GCC issues an error for constant initializers, and a warning for non-constants, but both should SFINAE.Boltrope
D
7

The standard never says anything "should not compile" (except for #error). Certain ill-formed programs must emit a diagnostic and issuing a warning satisfies that.

You can cause gcc to stop compilation on all diagnostics by using the switch -Werror. It can be narrowed to specific warnings, e.g. -Werror=narrowing.

If you are compiling in GNU++ or whatever the default mode is instead of C++11 then the compiler can do whatever it likes, including accepting narrowing conversions without complaint.

Reference: N3936 [intro.compliance]/2

  • If a program contains a violation of any diagnosable rule [...], a conforming implementation shall issue at least one diagnostic message.

  • If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program.

[defns.diagnostic]

diagnostic message

message belonging to an implementation-defined subset of the implementation’s output messages

Note also from the first bullet point that it is not required that the number or content of messages corresponds to the number or content of the violations.

The standard leaves it completely up to the compiler to decide how to organize its errors and/or warnings, with the proviso that for certain violations it can't silently ignore it.

Drawbridge answered 11/2, 2015 at 23:0 Comment(6)
Thanks. The fact that confused me is that if you look in the standard, you see the narrowing declarations marked with an // error comment, same as e.g. int& r; // error. Now, common sense tells me that when you comment something with error, it should not be compilable. I have to admit that I did not know that "ill-formed programs" are just required to emit a diagnostic and not a compile time error.Romilda
This answer would be more complete by stating where in the standard it says this.Jolson
@Romilda It's up to gcc/g++'s developers to decide the default setting for whether a particular condition is reported as "error" or "warning". I presume they try to go with what their userbase would complain about the leastDrawbridge
@MattMcNabb thanks for the effort, I certainly didn't know this.Romilda
Pradhan also makes a good point that the compiler would typically use "warning" for conditions where it has an extension in placeDrawbridge
The C++ standard doesn't even say #error should not compile, unlike C. See open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#745Boltrope
K
2

Quoting from 1.4 [intro.compliance]

A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs.

The applicable section for your initialization example is 8.5.4 [dcl.init.list]. In particular,

Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E , 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.

accompanied by the example

int x1 {2}; // OK
int x2 {2.0}; // error: narrowing

Since the exact nature of the diagnostic is implementation specified, both sets of behaviours observed are standard compliant.

Krall answered 11/2, 2015 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.