GCC, clang disagree with MSVC on narrowing conversion - c++

Consider the following program:
struct uint1 {
unsigned x;
uint1(unsigned x_) : x(x_) { }
};
struct foo { uint1 a; };
foo f(int v) {
return {v};
}
struct bar { unsigned a; };
bar g(int v) {
return {v};
}
Now, where do we have narrowing conversions here?
Compiler
version
narrowing conversion in f()?
narrowing conversion in g()?
GCC
11.2
No
Yes
clang
13.0
No
Yes
MSVC
19.4
Yes
Yes
Which is right? Naively, it seems to me there's a narrowing conversion in both functions.
See it all on GodBolt.
Note: C++17 in case it matters.

It seems that GCC and Clang are right in not classing this as a narrowing conversion.
The initialisation in question is that of a foo object. Its constructor argument is enclosed in {}. There cannot be a narrowing conversion in this initialisation, because the argument of the constructor is not of a numeric type. It is struct uint1.
So where does struct uint1 cone from? It is converted from v, but not by means of braced initialisation. The pair of braces around v is already occupied by the constructor of uint1.
So this is equivalent to foo{uint1(v)}.
MSVC on the other hand seems to interpret this as foo(uint1{v}), and I don't think this interpretation is justified.
Neither compiler complains about an explicit foo{uint1(v)}, as is required by the standard, because there is no narrowing at the top level.

Related

Enum bitfield and aggregate initialization

The following code is accepted by clang 6.0.0 but rejected by gcc 8.2
enum class E {
Good, Bad,
};
struct S {
E e : 2;
int dummy;
};
S f() {
return {E::Good, 100};
}
Live godbolt example
The GCC complains
error: could not convert '{Good, 100}' from '<brace-enclosed initializer list>' to 'S'
Which one is correct? Where in the standard talks about this situation?
return {E::Good, 100}; performs copy list initialization of the return value. The effect of this list initialization is an aggregate initialization.
So is S an aggregate? The description of an aggregate varies depending on which version of C++ you're using, but in all cases S should be an aggregate so this should compile. Clang (and MSVC) have the correct behavior.
The fix is simple, though. Change your return statement to return the correctly typed object:
return S{E::Good, 100};
This should be well-formed and so this is a gcc bug.
We end up at aggregate initialization via [stmt.return]p2 which says:
… A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization ([dcl.init.list]) from the specified initializer list. …
then [dcl.init.list]p3.2 says:
Otherwise, if T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]). …
At this point we may wonder if this is a narrowing conversion and therefore ill-formed but [dcl.init.list]p7 does not have any clauses that cover this case and no other cases in [dcl.init.list] apply to make this ill-formed.
We can see with a similar example which removes enum from the equation but keeps the bit-field shows neither gcc nor clang gives us a narrowing conversion diagnostic, which we expect to be the case, although this similar problem is covered by [dcl.init.list]p7.4 although not ill-formed:
struct S2 {
int e : 2 ;
int dummy ;
} ;
S2 foo( int x ) {
return {x, 100} ;
}
see it live in godbolt
As observed gcc does not seem to have a problem in other contexts i.e.
S f(E e1, int x) {
S s {e1,100} ;
return s;
}
So you do have work-arounds available.

Narrowing conversion in pair with braced initializer compiling if second attribute is not braced-initialized

Since c++11, narrowing conversion is not allowed in list initialization (including aggregate initialization). So basically:
char c{1000}; // Does not compile with g++, clang, vc
But:
std::pair<char, double> p{1000, 1.0};
Compiles with all compiler?
But:
std::pair<char, double> p{1000, {1.0}};
Does not compile with VC (error C2398), gives a warning with clang and compiles silently with g++...
I would have expected VC behavior everywhere, i.e. a non-allowed narrowing conversion throwing an error. Which is compiler is right?
On the other hand, none of the variable declarations in the following snippets compile:
struct X {
char c;
double d;
};
X x1{999, 1.0};
X x2{999, {1.0}};
struct Y {
char c;
double d;
Y (char c, double d) : c(c), d(d) { }
};
Y y1{999, 1.0};
Y y2{999, {1.0}};
So one of my guess may be that there is something special about std::pair? Something that would also narrowing braced-initialization?
std::pair<char, double> p{1000, 1.0}; is not diagnosed because it calls the template<class U1, class U2> pair(U1&&, U2&&) constructor (with U1 == int and U2 == double), which is an exact match; the narrowing doesn't happen until you get into the constructor body.
std::pair<char, double> p{1000, {1.0}}; cannot call that constructor because the braced-init-list {1.0} is a non-deduced context, so you cannot deduce U2.
In clang/libc++, it calls the pair(const T1&, const T2&) constructor; however clang apparently doesn't consider the conversion needed to create a temporary for reference binding as part of the narrowing check. This is probably a bug.
GCC/libstdc++'s pair has a template<class U1> pair(U1&&, const T2&) constructor template, which is a better match. With this constructor, the second argument isn't narrowing, and the first argument is an exact match, so you get no error or warning.

Could non-static member variable be modified in constexpr constructor (C++14)?

struct A {
int a = 0;
constexpr A() { a = 1; }
};
constexpr bool f() {
constexpr A a;
static_assert(a.a == 1, ""); // L1: OK
return a.a == 1;
}
static_assert(f(), ""); // L2: Error, can not modify A::a in constexpr
Online Compiler URL: http://goo.gl/jni6Em
Compiler: clang 3.4 (with -std=c++1y)
System: Linux 3.2
If I delete L2, this code compiles. If I add L2, the compiler complained "modification of object of const-qualified type 'const int' is not allowed in a constant expression". I am not a language lawyer, so I am not sure whether this is true. However, if it is, why compiler didn't complain anything about L1, since it also called A() as constexpr? Is this a bug of clang? Or did I miss anything?
Reference: http://en.cppreference.com/w/cpp/language/constexpr
BTW, if I change "constexpr A a;" to "A a;" (remove constexpr keyword), L1 failed to compile which is expect. However, the compiler didn't complain about L2 anymore.
Online Compiler URL about this: http://goo.gl/AoTzYx
I believe this is just a case of compilers not having caught up to the changes proposed for C++14. Your constexpr constructor satisfies all the conditions listed in §7.1.5/4 of N3936. Both gcc and clang fail to compile your code, but for different reasons.
clang complains:
note: modification of object of const-qualified type 'const int' is not allowed in a constant expression
which doesn't make much sense, but reminds me of the C++11 restriction that constexpr member functions are implicitly const (this is a constructor, and that doesn't apply, but the error message is reminiscent of that). This restriction was also lifted for C++14.
gcc's error message is:
error: constexpr constructor does not have empty body
Seems pretty clear that gcc still implements the C++11 rules for constexpr constructors.
Moreover, N3597 lists this example:
struct override_raii {
constexpr override_raii(int &a, int v) : a(a), old(a) {
a = v;
}
constexpr ~override_raii() {
a = old;
}
int &a, old;
};
N3597 was superseded by N3652, which contains the wording found in the current draft. Unfortunately, the earlier example disappears, but, again, nothing in the current wording says you cannot assign values to data members within the body of a constexpr constructor.
Update (2017-10-03)
clang fixed this, but there has been no new release yet: https://bugs.llvm.org/show_bug.cgi?id=19741
(Compiler explorer)

Is there a way to prevent implicit static_casts when calling a function?

C++11 adds this.
int x;
unsigned int y{ x }; // ERROR
Is it possible to enable something like this.
int x;
void f(unsigned int y);
f(x); //ERROR
Compiler: VC++ 2013
Try this (live example):
template <
typename T,
typename = typename std::enable_if <std::is_same <T, unsigned int>{}>::type
>
void f(T x) { }
No, there's no compiler switch or other general setting to do that. Implicit conversions are part of the language, and cannot be disabled in the general case for built-in types. The closest you can get is a user-defined wrapper class with only explicit constructors, or applying some template meta-hackery to the function you're trying to call (as in iavr's answer).
The premise of your question appears to conflate implicit conversions with the special case of narrowing conversions. The following:
int x = 0;
unsigned int y{ x };
is not an error. You may get a warning about the potential narrowing conversion, and this warning may be turned into an error (with GCC's -Werror, for example), but this is not new in C++11 and it does not inherently prohibit the conversion.
The program is only ill-formed if you actually cause a narrowing conversion. Again, this is specific to the value involved and is not a general rule about implicit conversions.

Preventing narrowing conversion when using std::initializer_list

#include <iostream>
struct X {
X(std::initializer_list<int> list) { std::cout << "list" << std::endl; }
X(float f) { std::cout << "float" << std::endl; }
};
int main() {
int x { 1.0f };
X a(1); // float (implicit conversion)
X b{1}; // list
X c(1.0f); // float
X d{1.0f}; // list (narrowing conversion) ARG!!!
// warning: narrowing conversion of '1.0e+0f' from 'float' to 'int'
// inside { } [-Wnarrowing]
}
Is there any other way of removing std::initializer_list from an overload list (i.e., making the non-list ctors more favorable) instead of using the ()-initialization, or at least prohibiting narrowing conversion to happen (apart from turning warning into error)?
I was using http://coliru.stacked-crooked.com/ compiler which uses GCC 4.8.
Actually, a program containing a narrowing conversion in a brace list initializer is ill-formed. I am not sure why the compiler just gives you a warning, but it definitely should issue an error here (FWIW, Clang does that).
Also notice, that this is a narrowing (and therefore illegal) conversion as well:
int x { 1.0f }; // ERROR! Narrowing conversion required
Per paragraph 8.5.4/3 of the C++11 Standard:
List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate, aggregate initialization is performed (8.5.1). [...]
— Otherwise, if the initializer list has no elements [...]
— Otherwise, if T is a specialization of std::initializer_list<E>, [...]
— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated
and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see
below) is required to convert any of the arguments, the program is ill-formed. [...]
To be more precise, the Standard only says that a "diagnostic" is required in this case, and a warning is a diagnostic, so the compiler's behavior is conforming - but I believe emitting an error would be a better behavior.
That looks like a compiler error. You should be getting an error instead of a warning. Brace initialization should never implicitly narrow.
From the standard (§ 8.5.4)
struct B {
B(std::initializer_list<int>);
};
B b1 { 1, 2 }; // creates initializer_list<int> and calls constructor
B b2 { 1, 2.0 }; // error: narrowing
You can achieve what you want with std::enable_if.
#include <iostream>
#include <type_traits>
struct X {
template<typename T, typename = typename std::enable_if<std::is_same<T,int>::value>::type>
X(std::initializer_list<T>) { std::cout << "list" << std::endl; }
X(float) { std::cout << "float" << std::endl; }
};
int main() {
X a(1); // float (implicit conversion)
X b{1}; // list
X c(1.0f); // float
X d{1.0f}; // float (yay)
}
Works on both g++4.8 and clang 3.2
You can use -Wno-c++11-narrowing to turn off the errors:
Here's a sample test program:
#include <cstdint>
struct foo {
int32_t a;
};
void foo(int64_t val) {
struct foo A = { val };
}
Compile with clang++-3.8 with just -std=c++11, we get the stated error:
Add -Wno-c++11-narrowing, golden silence :-)
Of course, the narrowing issue might come back to bite you later, but it might occasionally be easier to delay the technical debt pain till later. ymmv :-)