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