#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 :-)
Related
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.
Consider the following code:
struct A{
int x;
int y;
};
struct B{
int y;
int x;
};
void func (A){
}
void func (B){
}
int main()
{
func({.y=1,.x=1});
}
For some reason both clang and gcc consider this code ambiguous even though it is known that order in designated initializer must match the order in struct, although for some reason clang allows it and just issues a warning:
ISO C++ requires field designators to be specified in declaration
order; field 'y' will be initialized after field 'x'
[-Wreorder-init-list]
Now the fun begins:
If I comment out the func(B) code goes from ambiguous to not compiling.
That is what I consider super weird. Is there some logic behind this?
If the cause of my confusion is not clear:
if we have both func(A) and func(B) in code both gcc and clang consider the func({.y=1,.x=1}) ambiguous, but if I remove func(B) from source then it gives an error(or warning in case of clang). In other words we went from 2 options to 0 options by removing 1 option.
godbolt
The rule is in [over.ics.list]/2:
If the initializer list is a designated-initializer-list, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization ([dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.
[Note 1: Aggregate initialization does not require that the members are declared in designation order. If, after overload resolution, the order does not match for the selected overload, the initialization of the parameter will be ill-formed ([dcl.init.list]).
[Example 1:
struct A { int x, y; };
struct B { int y, x; };
void f(A a, int); // #1
void f(B b, ...); // #2
void g(A a); // #3
void g(B b); // #4
void h() {
f({.x = 1, .y = 2}, 0); // OK; calls #1
f({.y = 2, .x = 1}, 0); // error: selects #1, initialization of a fails
// due to non-matching member order ([dcl.init.list])
g({.x = 1, .y = 2}); // error: ambiguous between #3 and #4
}
— end example]
— end note]
Basically, the rule that the designators have to name members is in [dcl.init.aggr] (so it counts for whether you can form a conversion sequence) but the rule that designators have to be in order is in [dcl.init.list] (so it's just like a later requirement that happens, and does not affect whether a conversion sequence can be formed - so it's not "sfinae-friendly").
In other words, this is fine:
struct A { int a; };
struct B { int b; };
void f(A);
void f(B);
void call_f() {
f({.a=1}); // ok, calls f(A)
}
Because you cannot initialize a B from {.a=1} according to the rules for aggregate initialization.
But your example (and the one quoted above) are ambiguous, since both satisfy the aggregate initialization rules but fail later.
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.
Is there any potential semantic difference when I use trailing comma during uniform initialization?
std::vector< std::size_t > v1{5, }; // allowed syntax
std::vector< std::size_t > v2{10};
Can I use trailing comma to make compiler to select std::vector::vector(std::initializer_list< std::size_t >) constructor instead of std::vector::vector(std::size_t, const std::size_t &) or are there any other tricks with mentioned syntax?
Can I use it to detect is there std::initializer_list-constructor overloading?
Considering the following code, which constructor must be selected?
struct A { A(int) { ; } A(double, int = 3) { ; } };
A a{1};
A b{2, };
This code is accepted by gcc 8 and A(int) is selected in both cases.
First, The C++ grammar rules makes the trailing , optional for braced-init-list. To quote dcl.init/1
A declarator can specify an initial value for the identifier being
declared. The identifier designates a variable being initialized. The
process of initialization described in the remainder of [dcl.init]
applies also to initializations specified by other syntactic contexts,
such as the initialization of function parameters ([expr.call]) or the
initialization of return values ([stmt.return]).
initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
braced-init-list:
{ initializer-list ,opt }
{ designated-initializer-list ,opt }
{ }
Secondly, you can't pretty much override the overload resolution system. It will always use the std::initializer_list constructor if you use such syntax and such std::initializer_list constructor is available.
dcl.init.list/2:
A constructor is an initializer-list constructor if its first
parameter is of type std::initializer_list or reference to
possibly cv-qualified std::initializer_list for some type E, and
either there are no other parameters or else all other parameters have
default arguments.
[ Note: Initializer-list constructors are favored over other constructors in list-initialization ([over.match.list])....
The program below prints Using InitList:
#include <iostream>
#include <initializer_list>
struct X{
X(std::initializer_list<double>){ std::cout << "Using InitList\n"; }
X(int){ std::cout << "Using Single Arg ctor\n"; }
};
int main(){
X x{5};
}
Despite the fact that 5 is a literal of type int, it should have made sense to select the single argument constructor since its a perfect match; and the std::initializer_list<double> constructor wants a list of double. However, the rules favour std::initializer_list<double> because its an initializer-list constructor.
As a result, even the program below fails because of narrowing conversion:
#include <iostream>
#include <initializer_list>
struct Y{
Y(std::initializer_list<char>){ std::cout << "Y Using InitList\n"; }
Y(int, int=4){ std::cout << "Y Using Double Arg ctor\n"; }
};
int main(){
Y y1{4777};
Y y2{577,};
Y y3{57,7777};
}
In response to your comment below, "what if there is no overloading with std::initializer_list, or it is not the first constructor's parameter?" - then overload resolution doesn't choose it. Demo:
#include <iostream>
#include <initializer_list>
struct Y{
Y(int, std::initializer_list<double>){ std::cout << "Y Using InitList\n"; }
Y(int, int=4){ std::cout << "Y Using Double Arg ctor\n"; }
};
int main(){
Y y1{4};
Y y2{5,};
Y y3{5,7};
}
Prints:
Y Using Double Arg ctor
Y Using Double Arg ctor
Y Using Double Arg ctor
If there is no initializer-list constructor available, then the {initializer-list...,} initializer pretty much falls back to direct initialization as per dcl.init/16, whose semantics are covered by the proceeding paragraph of dcl.init/16
No. That comma is a concession to make preprocessor macro tricks work without compile errors. It means nothing about your data type or its size.
Consider the following:
struct A {
A(int, int) { }
};
struct B {
B(A ) { } // (1)
explicit B(int, int ) { } // (2)
};
int main() {
B paren({1, 2}); // (3)
B brace{1, 2}; // (4)
}
The construction of brace in (4) clearly and unambiguously calls (2). On clang, the construction of paren in (3) unambiguously calls (1) where as on gcc 5.2, it fails to compile with:
main.cpp: In function 'int main()':
main.cpp:11:19: error: call of overloaded 'B(<brace-enclosed initializer list>)' is ambiguous
B paren({1, 2});
^
main.cpp:6:5: note: candidate: B::B(A)
B(A ) { }
^
main.cpp:5:8: note: candidate: constexpr B::B(const B&)
struct B {
^
main.cpp:5:8: note: candidate: constexpr B::B(B&&)
Which compiler is right? I suspect clang is correct here, as the ambiguity in gcc can only arise through a path that involves implicitly constructing B{1,2} and passing that to the copy/move constructor - yet that constructor is marked explicit, so such implicit construction should not be allowed.
As far as I can tell, this is a clang bug.
Copy-list-initialization has a rather unintuitive behaviour: It considers explicit constructors as viable until overload resolution is completely finished, but can then reject the overload result if an explicit constructor is chosen. The wording in a post-N4567 draft, [over.match.list]p1
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations (13.3.1.3, 13.3.1.4), where only converting constructors
are considered for copy-initialization. This restriction only applies
if this initialization is part of the final result of overload
resolution. — end note ]
clang HEAD accepts the following program:
#include <iostream>
using namespace std;
struct String1 {
explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
String2(const char*) { cout << "String2\n"; }
};
void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }
int main()
{
//f1( {"asdf"} );
f2( {"asdf"} );
f( {"asdf"} );
}
Which is, except for commenting out the call to f1, straight from Bjarne Stroustrup's N2532 - Uniform initialization, Chapter 4. Thanks to Johannes Schaub for showing me this paper on std-discussion.
The same chapter contains the following explanation:
The real advantage of explicit is that it renders f1("asdf") an
error. A problem is that overload resolution “prefers” non-explicit
constructors, so that f("asdf") calls f(String2). I consider the
resolution of f("asdf") less than ideal because the writer of
String2 probably didn’t mean to resolve ambiguities in favor of
String2 (at least not in every case where explicit and non-explicit
constructors occur like this) and the writer of String1 certainly
didn’t. The rule favors “sloppy programmers” who don’t use explicit.
For all I know, N2640 - Initializer Lists — Alternative Mechanism and Rationale is the last paper that includes rationale for this kind of overload resolution; it successor N2672 was voted into the C++11 draft.
From its chapter "The Meaning Of Explicit":
A first approach to make the example ill-formed is to require that all
constructors (explicit and non-explicit) are considered for implicit
conversions, but if an explicit constructor ends up being selected,
that program is ill-formed. This rule may introduce its own surprises;
for example:
struct Matrix {
explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);
struct Pixel {
Pixel(int row, int col);
};
Pixel transpose(Pixel);
Pixel p = transpose({x, y}); // Error.
A second approach is to ignore the explicit constructors when looking
for the viability of an implicit conversion, but to include them when
actually selecting the converting constructor: If an explicit
constructor ends up being selected, the program is ill-formed. This
alternative approach allows the last (Pixel-vs-Matrix) example to work
as expected (transpose(Pixel) is selected), while making the
original example ("X x4 = { 10 };") ill-formed.
While this paper proposes to use the second approach, its wording seems to be flawed - in my interpretation of the wording, it doesn't produce the behaviour outlined in the rationale part of the paper. The wording is revised in N2672 to use the first approach, but I couldn't find any discussion about why this was changed.
There is of course slightly more wording involved in initializing a variable as in the OP, but considering the difference in behaviour between clang and gcc is the same for the first sample program in my answer, I think this covers the main points.
This is not a complete answer, even though it is too long as a comment.
I'll try to propose a counterexample to your reasoning and I'm ready to see downvote for I'm far from being sure.
Anyway, let's try!! :-)
It follows the reduced example:
struct A {
A(int, int) { }
};
struct B {
B(A) { }
explicit B(int, int ) { }
};
int main() {
B paren({1, 2});
}
In this case, the statement {1, 2} gives place apparently to two solutions:
direct initialization by means of B(A), because A(int, int) is not explicit and thus it is allowed and that's actually the first candidate
for the same reason above, it can be interpreted as B{B(A{1,2})} (well, let me abuse the notation to give you an idea and what I mean), that is {1,2} allows the construction of a B temporary object that is used immediately after as an argument for the copy/move constructor, and it's allowed again because the involved constructors are not explicit
The latter would explain the second and the third candidates.
Does it make sense?
I'm ready to delete the answers as long as you explain me what's wrong in my reasoning. :-)