Bug 44500 - [C++0x] Bogus narrowing conversion error
Summary: [C++0x] Bogus narrowing conversion error
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: unknown
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-06-11 10:45 UTC by Gunther Piez
Modified: 2011-03-24 11:45 UTC (History)
3 users (show)

See Also:
Host: x86_64-pc-linux-gnu
Target: x86_64-pc-linux-gnu
Build: x86_64-pc-linux-gnu
Known to work:
Known to fail:
Last reconfirmed: 2010-07-09 21:29:47


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Gunther Piez 2010-06-11 10:45:46 UTC
Compiling with g++ -std=c++0x, using gcc-4.5.0 :

struct A {
	char x;
};

template<char C> void f() {
	char y = 42;
	A a = { y+C };
}

int main() {
	f<1>();
}

yields an "error: narrowing conversion of ‘(((int)y) + 8)’ from ‘int’ to ‘char’ inside { }".
If I change the template parameter type from "char C" to "int C" the error message persists, this seems wrong too, but I am not quite shre.

If I leave out the "y", everything is fine.
Comment 1 Manuel López-Ibáñez 2010-06-11 10:57:21 UTC
I wonder what the C++ standard said because we have the same issue in Wconversion and Joseph rejected my patch arguing that the operation was done in the larger type, so there was a narrowing conversion. I still believe that we should not warn for T = T op T, when T is the same type all the time.
Comment 2 Gunther Piez 2010-06-11 11:34:04 UTC
Sorry for the unicode mess. The error message is 'error: narrowing conversion of "(((int)y) + 1)" from "int" to "char" inside { }'.

The same error happens with a non templated function, but if I use two template parameters, the error disappears, even if they are to large. So this is at least very inconsistent.


no error:

struct A {                                                                                                           
<-->char x;                                                                                                          
};                                                                                                                   
                                                                                                                     
template<char C, char D>void f() {                                                                                     
<-->A a = { C+D };                                                                                                   
}                                                                                                                    
                                                                                                                     
int main() {                                                                                                         
<-->f<1,2>();                                                                                                        
}                                                                                                                    




still no error:

struct A {                                                                                                           
<-->char x;                                                                                                          
};                                                                                                                   
                                                                                                                     
template<int C, int D>void f() {                                                                                     
<-->A a = { C+D };                                                                                                   
}                                                                                                                    
                                                                                                                     
int main() {                                                                                                         
<-->f<1,2>();                                                                                                        
}                                                                                                                    


error:

struct A {                                                                                                           
<-->char x;                                                                                                          
};                                                                                                                   
                                                                                                                     
void f(char C, char D) {                                                                                     
<-->A a = { C+D };                                                                                                   
}                                                                                                                    
                                                                                                                     
int main() {                                                                                                         
<-->f(1,2);                                                                                                        
}                                                                                                                    



I believe I should not get an error, even if the template parameter type is larger than a char, as long as the template parameter value fits in in char, so

template<int C> void f() {
        char y = 42;
        A a = { y+C };
}

should give no error, as long as C fits in a char. IMHO ;-)
Comment 3 Jonathan Wakely 2010-06-11 11:37:41 UTC
'y' and 'C' are both promoted to int and 'y' is not a constant, so the resulting value cannot be proven to fit in a char, so it's a narrowing conversion.

If you make 'y' a const int then the value y+C is a constant and can be proven not to narrow.

The fact that the optimiser knows y=42 is irrelevant, the language specifies that y is not a constant, and whether the code is valid or not has to be independent of optimisations such as value propagation.

I think gcc is correct here.
Comment 4 Jonathan Wakely 2010-06-11 11:42:47 UTC
(In reply to comment #2)
> Sorry for the unicode mess. The error> I believe I should not get an error, even if the template parameter type is
> larger than a char, as long as the template parameter value fits in in char, so
> 
> template<int C> void f() {
>         char y = 42;
>         A a = { y+C };
> }
> 
> should give no error, as long as C fits in a char. IMHO ;-)

Just to be extra clear: C is a constant and the narrowing is *not* due to C, consider:

struct A {
        char x;
};

template<int C> void f() {
        A a = { C };
}

int main() {
        f<1>();
}

Here C is an int, but it's a constant and it is provable that no narrowing takes palce.

The problem in your example is 'y' not 'C' because the value of 'y' is not a constant.

 

Comment 5 Gunther Piez 2010-06-11 12:09:17 UTC
So is it provable that for a "T op T" to be stored in T no narrowing takes place?

If the answer for T == char is no and for T == int it is yes this is rather fishy ;-)
Comment 6 Jonathan Wakely 2010-06-11 12:51:28 UTC
(In reply to comment #5)
> So is it provable that for a "T op T" to be stored in T no narrowing takes
> place?

Yes, if the values are constants.

> If the answer for T == char is no and for T == int it is yes this is rather
> fishy ;-)

That's not what I said. Look:

#include <climits>

struct A {
        char x;
};

template<char C, char D> void f() {
        A a = { C+D };
}

template<int I, int J> void g() {
        A a = { I+J };
}

int main() {
        f<1, 1>();                // OK
        g<1, 1>();                // OK

        f<CHAR_MAX, CHAR_MAX>();  // Error
}

f<1,1>() is ok, because C and D are constants. The type doesn't matter, the result of C+D is known at compile time and fits in a char.

g<1,1>() is ok, because I and J are constants. The type doesn't matter, the result of I+J is known at compile time and fits in a char.

f<CHAR_MAX, CHAR_MAX>() is not ok, because the result is known at compile time and doesn't fit in a char.

See 8.5.4 [dcl.init.list]p6 in teh C++0x draft for the full rules



Comment 7 Manuel López-Ibáñez 2010-06-11 13:07:43 UTC
He is referring to a testcase like:

template<typename T, T C> void f() {
  struct A {
    T x;
  };

  T y = 42;
  A a = { y + C };
}

int main() {
  f<int,1>();
  f<char,1>();
}

So, we warn for T == char but not for T == int. I know that the standard considers differently narrowing and overflow but the difference is still surprising.
Comment 8 Jonathan Wakely 2010-06-11 13:20:24 UTC
(In reply to comment #7)
> He is referring to a testcase like:
> 
> template<typename T, T C> void f() {
>   struct A {
>     T x;
>   };
> 
>   T y = 42;
>   A a = { y + C };
> }
> 
> int main() {
>   f<int,1>();
>   f<char,1>();
> }
> 
> So, we warn for T == char but not for T == int. I know that the standard

Note it's not a warning, it's an error, mandated by the standard.

> considers differently narrowing and overflow but the difference is still
> surprising.

In both cases, T+T has type int, so obviously it fits in an int.  It doesn't necessarily fit in an char, so is an error unless the values are constants and the actual value can fit in a char.

This is mandated by the standard and the diagnostic is IMHO clear. 

Comment 9 Gunther Piez 2010-06-11 13:27:05 UTC
I understand now after the implicit promotion to int of a non constant value the result of the narrowing operation can't be guaranteed to fit in the original type. But I still think it shouldn't give an error, and if the standard says so, I think it is flawed in this regard ;-)

Consider

g<INT_MAX, INT_MAX>();  // Warning, but no Error

despite it can be proven that the value will not fit and this is very likely an error. Opposing to

char c,d;
A a = { c+d };

which is very likely not an error and would only require a mild warning. IMHO.

Manuel, in your testcase, you do not only warn, you error out if compiled with -std=c++0x.
Comment 10 Manuel López-Ibáñez 2010-06-11 13:33:31 UTC
(In reply to comment #8)
> 
> In both cases, T+T has type int, 

We know that, but I don't think most C/C++ programmers know about integer promotion rules. We just disagree here. But since this is mandated by the standard, you are right.

> so obviously it fits in an int.  It doesn't

For a strict-standard definition of "fits", because it may overflow and a layman wouldn't say that it "fits" in that case.

> This is mandated by the standard and the diagnostic is IMHO clear. 

I am not arguing against that (although, I think it is unfortunate). I would prefer a bit longer message:

error: C++0x does not allow narrowing conversion of "(((int)y) + 1)" from "int" to "char" inside { }

(BTW, I think those braces should be within quotes).

But since I guess I am in the minority here, we'll have to close this as INVALID. 

Comment 11 Jonathan Wakely 2010-06-11 14:56:03 UTC
(In reply to comment #9)
> I understand now after the implicit promotion to int of a non constant value
> the result of the narrowing operation can't be guaranteed to fit in the
> original type. But I still think it shouldn't give an error, and if the
> standard says so, I think it is flawed in this regard ;-)
> 
> Consider
> 
> g<INT_MAX, INT_MAX>();  // Warning, but no Error

The integer overflow means this is undefined behaviour. But it is not a narrowing conversion according to the rules of 8.5.4/6


> despite it can be proven that the value will not fit and this is very likely an
> error. Opposing to
> 
> char c,d;
> A a = { c+d };
> 
> which is very likely not an error and would only require a mild warning. IMHO.

use A a = { char(c+d) } if you want the result to be a char not an int, then there is no narrowing conversion, because a narrowing conversion is an impliit conversion.


(In reply to comment #10)iagnostic is IMHO clear. 
> prefer a bit longer message:
> 
> error: C++0x does not allow narrowing conversion of "(((int)y) + 1)" from "int"
> to "char" inside { }

I prefer the shorter message. If the compiler tells you there is an error it doesn't normally tell you the standard says so. If you compile with -std=c++0x then obviously that's the standard in question.
Comment 12 Gunther Piez 2010-06-12 08:46:39 UTC
I am closing this, as it isn't a gcc bug, as it behaves according to the standard.

The bug is in the standard, as it mandates

f<1,1>                  // ok
f<CHAR_MAX, CHAR_MAX>() // error
g<INT_MAX, INT_MAX>()   // no error, but undefined behaviuour

f(char, char)           // error
g(int, int)             // ok

which is inconsistent and surprising. C++0x should really have got rid of the implicit integer promotion. Wasn't the intent of the implicit promotion to be able to write 

char a,b,c,d;
a = b*c/d;

and get a correct result even if b*c > CHAR_MAX? I believe nobody does write code like this anymore, and even if, you could simply say "undefined behaviour" ;-) It doesn't work for ints anyway.

Instead I have now an implicit integer promotion which forces me to use an explicit cast in compound initializers, where narrowing conversion isn't allowed, while in a simple assignment of course it is allowed (or else a hell would break loose... ). Why not make -Wconversion an error, at least this would be consistent ;-)
Comment 13 Gunther Piez 2010-06-12 08:47:17 UTC
...
Comment 14 Jonathan Wakely 2010-06-13 17:14:41 UTC
(In reply to comment #12)
> Why not make -Wconversion an error, at least this would
> be consistent ;-)

You can use -Werror=conversion 
Comment 15 Jason Merrill 2010-07-09 21:29:33 UTC
I agree that this is a bug, but it's a bug in C++0x, not with GCC, and unfortunately it came up too late for me to include it as a national body comment.  I have submitted it as a defect report.
Comment 16 Jason Merrill 2011-03-24 08:58:46 UTC
The committee closed my DR as not a defect.  We could still downgrade this error to a pedwarn, though.

http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_closed.html#1078
Comment 17 Jason Merrill 2011-03-24 08:59:43 UTC
Actually, it's already a permerror, so -fpermissive will allow your code to compile without changes.
Comment 18 Gunther Piez 2011-03-24 11:45:47 UTC
I have chosen the "recommended" way and added a cast, -fpermissive would allow to many other dubious constructs to pass. Still I think c++ should get rid of implicit integer conversions :-)