-->

g++4.9 and g++5 different behaviour when narrowing

2020-02-14 06:29发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.