c++ - C++11 constructor overload resolution and initialiser_lists: clang++ and g++ disagree -
i have small piece of c++11 code g++ (4.7 or 4.8) refuses compile claiming call constructor b2 b2a(x, {p(y)}) ambiguous. clang++ happy code, refuses compile b2 b2b(x, {{p(y)}}) g++ happy compile!
both compilers happy b1 constructor either {...} or {{...}} argument. can c++ language lawyer explain compiler correct (if either) , going on? code below:
#include <initializer_list> using namespace std; class y {}; class x; template<class t> class p { public: p(t); }; template<class t> class { public: a(initializer_list<t>); }; class b1 { public: b1(const x&, const y &); b1(const x&, const a<y> &); }; class b2 { public: b2(const x &, const p<y> &); b2(const x &, const a<p<y>> &); }; int f(const x &x, const y y) { b1 b1a(x, {y}); b1 b1b(x, {{y}}); b2 b2a(x, {p<y>(y)}); b2 b2b(x, {{p<y>(y)}}); return 0; }
and compiler errors, clang:
$ clang++ -stdlib=libc++ -std=c++11 test-initialiser-list-4.cc -o test.o -c test-initialiser-list-4.cc:32:6: error: call constructor of 'b2' ambiguous b2 b2(x, {{p<y>(y)}}); ^ ~~~~~~~~~~~~~~ test-initialiser-list-4.cc:26:5: note: candidate constructor b2(const x &, const p<y> &); ^ test-initialiser-list-4.cc:27:5: note: candidate constructor b2(const x &, const a<p<y>> &); ^
g++:
test-initialiser-list-4.cc: in function 'int f(const x&, y)': test-initialiser-list-4.cc:32:21: error: call of overloaded 'b2(const x&, <brace-enclosed initializer list>)' ambiguous b2 b2(x, {p<y>(y)}); ^ test-initialiser-list-4.cc:32:21: note: candidates are: test-initialiser-list-4.cc:27:5: note: b2::b2(const x&, const a<p<y> >&) b2(const x &, const a<p<y>> &); ^ test-initialiser-list-4.cc:26:5: note: b2::b2(const x&, const p<y>&) b2(const x &, const p<y> &); ^
this smells interaction between uniform initialisation, initialiser list syntax , function overloading templated arguments (which know g++ stringent about), i'm not enough of standards lawyer able unpack should correct behaviour here!
first code, think should happen. (in follows, ignore first parameter, since interested second parameter. first 1 exact match in example). please note rules in flux in spec, wouldn't 1 or other compiler has bug.
b1 b1a(x, {y});
this code cannot call const y&
constructor in c++11, because y
aggregate , y
has no data member of type y
(of course) or else initializable (this ugly, , worked on fixed - c++14 cd doesn't have wording yet, not sure whether final c++14 contain fix).
the constructor const a<y>&
parameter can called - {y}
taken argument constructor of a<y>
, , initialize constructor's std::initializer_list<y>
.
hence - second constructor called successfully.
b1 b1b(x, {{y}});
here, same argument counts counts constructor const y&
parameter.
for constructor parameter type const a<y>&
, bit more complicated. rule conversion cost in overload resolution computing cost of initializing std::initializer_list<t>
requires every element of braced list convertible t
. before said {y}
cannot converted y
(as aggregate). important know whether std::initializer_list<t>
aggregate or not. frankly, have no idea whether or not must considered aggregate according standard library clauses.
if take non-aggregate, considering copy constructor of std::initializer_list<y>
, again trigger exact same sequence of tests (leading "infinite recursion" in overload resolution checking). since rather weird , non-implementable, don't think implementation takes path.
if take std::initializer_list
aggregate, saying "nope, no conversion found" (see above aggregates-issue). in case since cannot call initializer constructor single initializer list whole, {{y}}
split multiple arguments, , constructor(s) of a<y>
taking each of separately. hence, in case, end {y}
initializing std::initializer_list<y>
single parameter - fine , work charm.
so under assumption std::initializer_list<t>
aggregate, fine , call second constructor successfully.
b2 b2a(x, {p<y>(y)});
in case , next case, don't have aggregate issue above y
anymore, since p<y>
has user-provided constructor.
for p<y>
parameter constructor, parameter initialized {p<y> object}
. p<y>
has no initializer lists, list split individual arguments , call move-constructor of p<y>
rvalue object of p<y>
.
for a<p<y>>
parameter constructor, same above case a<y>
initialized {y}
: since std::initializer_list<p<y>>
can initialized {p<y> object}
, argument list not split, , hence braces used initializer constructor's std::initializer_list<t>
.
now, both constructors work fine. acting overloaded functions here, , second parameter in both cases requires user defined conversion. user defined conversion sequences can compared if in both cases same conversion function or constructor used - not case here. hence, ambiguous in c++11 (and in c++14 cd).
note here have subtle point explore
struct x { operator int(); x(){/*nonaggregate*/} }; void f(x); void f(int); int main() { x x; f({x}); // ambiguity! f(x); // ok, calls first f }
this counter intuitive result fixed in same run fixing aggregate-initialization weirdness mentioned above (both call first f). implemented saying {x}->x
becomes identity conversion (as x->x
). currently, user-defined conversion.
so, ambiguity here.
b2 b2b(x, {{p<y>(y)}});
for constructor parameter const p<y>&
, again split arguments , {p<y> object}
argument passed constructor(s) of p<y>
. remember p<y>
has copy constructor. complication here not allowed use (see 13.3.3.1p4), because require user defined conversion. constructor left 1 taking y
, y
cannot initialized {p<y> object}
.
for constructor parameter a<p<y>>
, {{p<y> object}}
can initialize std::initializer_list<p<y>>
, because {p<y> object}
convertible p<y>
(other y
above - dang, aggregates).
so, second constructor called successfully.
summary 4
- second constructor called successfully
- under assumption
std::initializer_list<t>
aggregate, fine , call second constructor successfully - ambiguity here
- second constructor called successfully
Comments
Post a Comment