C ++ 11 constructor overload resolution and initialiser_lists: clang ++ and g ++ disagree

I have a small piece of C ++ 11 code that g ++ (4.7 or 4.8) refuses to compile, claiming that the constructor call for B2 b2a (x, {P (y)}) is ambiguous. Clang ++ is happy with this code, but refuses to compile B2 b2b (x, {{P (y)}}), which g ++ will compile fine!

Both compilers agree very well with the B1 constructor with the argument {...} or {{...}} as the argument. Can any C ++ lawyer explain which compiler is right (if any) and what happens? Code below:

#include <initializer_list> using namespace std; class Y {}; class X; template<class T> class P { public: P(T); }; template<class T> class A { 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 to constructor of 'B2' is 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>)' is 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 like the interaction between uniform initialization, the initializer list syntax and function overloading using template arguments (which I know about g ++ is pretty tough), but I don't have a standards lawyer to be able to unpack what the correct behavior should be here!

+8
c ++ c ++ 11 templates constructor-overloading ambiguous
source share
1 answer

The first code, what I think should happen. (In the future, I will ignore the first parameter, since we are only interested in the second parameter, the first of which is always an exact match in your example). Note that the rules currently apply in the specification, so I would not say that one or the other compiler has an error.

 B1 b1a(x, {y}); 

This code cannot call the const Y& constructor in C ++ 11, because Y is an aggregate, and Y does not have a data item of type Y (of course) or anything else initialized by it (this is something ugly and it is fixed - there is no wording on the C ++ 14 CD yet, so I'm not sure if there will be this fix in the latest C ++ 14).

You can call the constructor with the parameter const A<Y>& - {y} will be used as an argument of the constructor A<Y> and initializes this constructor std::initializer_list<Y> .

Therefore, the second constructor was successfully called.

 B1 b1b(x, {{y}}); 

Here basically the same argument counts the number of samples for the constructor with the const Y& parameter.

For a constructor with the parameter type const A<Y>& it is a bit more complicated. The rule for conversion cost when overload resolution calculates the initialization cost std::initializer_list<T> requires that each element of the parenthesis list be converted to T However, we said earlier that {y} cannot be converted to Y (since this is a collection). Now it is important to know whether std::initializer_list<T> collection or not. Honestly, I have no idea whether this should be considered an aggregate in accordance with standard library articles.

If we take it as a non-aggregate, then we will consider the copy constructor std::initializer_list<Y> , which, however, will repeat the same sequence of tests (which will lead to "infinite recursion" when checking overload resolution), since it is rather strange and unrealizable, I don't think any implementation takes this way.

If we take std::initializer_list as a collection, we will say β€œno, no conversion was found” (see the above problems with aggregates). In this case, since we cannot call the initializer constructor with a single list of initializers in general, {{y}} will be divided into several arguments, and the constructor A<Y> will take each of them separately. Therefore, in this case, we would end up with {y} initializing a std::initializer_list<Y> as the only parameter - which works fine and works like a charm.

So, assuming std::initializer_list<T> is an aggregate, this normally and successfully calls the second constructor.

 B2 b2a(x, {P<Y>(y)}); 

In this case and in the following case, we no longer have such a common problem, as indicated above, with Y , since P<Y> has a user-provided constructor.

For the parameter constructor P<Y> this parameter will be initialized {P<Y> object} . Since P<Y> has no initialization lists, the list will be split into separate arguments and will call the constructor move P<Y> with the rvalue object P<Y> .

For parameter constructor A<P<Y>> it is similar to the above case with A<Y> initialized {y} : Since std::initializer_list<P<Y>> can be initialized {P<Y> object} , the argument list is not broken, and therefore, braces are used to initialize the constructor std::initializer_list<T> .

Now both constructors are working fine. They act like overloaded functions here, and their second parameter in both cases requires a user-defined conversion. User-defined conversion sequences can only be compared if the same conversion function or constructor is used in both cases - this is not the case here. Therefore, this is ambiguous in C ++ 11 (and on the C ++ 14 CD).

Please note that here we have a thin point to study

 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 is likely to be fixed in the same run with the above initialization oddities fixed (both will call the first f). This is realized by saying that {x}->X becomes an identity transformation (as is X->x ). This is currently a custom conversion.

So, the ambiguity is here.

 B2 b2b(x, {{P<Y>(y)}}); 

For the constructor with the parameter const P<Y>& we again separate the arguments and get the argument {P<Y> object} passed to the constructor (s) P<Y> . Remember that P<Y> has a copy constructor. But the complication here is that we are not allowed to use it (see 13.3.3.1p4), since this will require a user-defined transformation. The only constructor on the left is the one that accepts Y , but Y cannot be initialized {P<Y> object} .

For a constructor with the parameter A<P<Y>> {{P<Y> object}} can initialize a std::initializer_list<P<Y>> , because {P<Y> object} can be converted to P<Y> (except for Y above - dang, aggregates).

So, the second constructor was successfully called.


Summary for all 4

  • second constructor successfully called
  • assuming std::initializer_list<T> is an aggregate, this normally and successfully calls the second constructor
  • ambiguity here
  • second constructor successfully called
+5
source share

All Articles