This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2024-12-19
Consider the following example:
#include <initializer_list> struct A{ operator short(){ return 0; } }; struct B{ operator bool(){ return 0; } }; void fun(std::initializer_list<int>){} void fun(std::initializer_list<bool>){} int main(){ fun({A{},B{}}); }
According to 12.2.4.2.6 [over.ics.list] paragraph 6,
Otherwise, if the parameter type is std::initializer_list<X> and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.
In this example, all of the conversions from list elements to the initializer_list template argument type are user-defined conversions. According to 12.2.4.3 [over.ics.rank] bullet 3.3,
User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if they contain the same user-defined conversion function or constructor or they initialize the same class in an aggregate initialization and in either case the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.
Since in both cases the two elements of the initializer-list argument involve different user-defined conversion functions, the two user-defined conversion sequences for the elements cannot be distinguished, so the determination of the “worst conversion” for the two candidates does not consider the second standard conversion sequence. This presumably makes it impossible to distinguish the conversion sequences for the two candidates in the function call, making the call ambiguous.
However, there is implementation divergence on the handling of this example, with g++ reporting an ambiguity and clang, MSVC, and EDG calling the int overload, presumably on the basis that short->int is a promotion while short->bool is a conversion.
Notes from the August, 2021 teleconference:
CWG agreed with the reasoning expressed in the analysis, that conversions involving different user-defined conversion functions cannot be compared, and thus the call is ambiguous. The use of the phrase “worst conversion” is insufficiently clear, however, and requires definition.
Proposed resolution, August, 2021:
Change 12.2.4.2.6 [over.ics.list] paragraphs 5 and 6 as follows:
Otherwise, if the parameter type is std::initializer_list<X> and either the initializer list is empty or all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the
worst conversionworst conversion necessary to convert an element of the list to X,or ifdefined as follows. If the initializer list has no elements, the worst conversion is the identity conversion. Otherwise, the worst conversion is an implicit conversion sequence for a list element that is not better than any other implicit conversion sequence required by list elements, compared as described in 12.2.4.3 [over.ics.rank]. If more than one implicit conversion sequence satisfies this criterion, then if they are user-defined conversion sequences that do not all contain the same user-defined conversion function or constructor, the worst conversion sequence is the ambiguous conversion sequence (12.2.4.2.1 [over.best.ics.general]); otherwise, it is unspecified which of those conversion sequences is chosen as worst. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor. [Example 2:void f(std::initializer_list<int>); f( {} ); // OK: f(initializer_list<int>) identity conversion f( {1,2,3} ); // OK: f(initializer_list<int>) identity conversion f( {'a','b'} ); // OK: f(initializer_list<int>) integral promotion f( {1.0} ); // error: narrowing struct A { A(std::initializer_list<double>); // #1 A(std::initializer_list<complex<double>>); // #2 A(std::initializer_list<std::string>); // #3 }; A a{ 1.0,2.0 }; // OK, uses #1 void g(A); g({ "foo", "bar" }); // OK, uses #3 typedef int IA[3]; void h(const IA&); h({ 1, 2, 3 }); // OK: identity conversion void x(std::initializer_list<int>); void x(std::initializer_list<bool>); struct S1 { operator short(); }; struct S2 { operator bool(); }; void y() { x({S1{}, S2{}}); // error: ambiguous. The ICSes for each list element are indistinguishable because // they do not contain the same conversion function, so the worst conversion is // the ambiguous conversion sequence. }—end example]
Otherwise, if the parameter type is “array of N X ” or “array of unknown bound of X”, if there exists an implicit conversion sequence from each element of the initializer list (and from {} in the former case if N exceeds the number of elements in the initializer list) to X, the implicit conversion sequence is the worst
such implicit conversion sequenceconversion necessary to convert an element of the list (including, if there are too few list elements, {}) to X, determined as described above for a std::initializer_list<X> with a non-empty initializer list.
CWG 2023-06-13
An ambiguous conversion for the function selected by overload resolution is ill-formed per 12.2.4.2.1 [over.best.ics.general]. Instead of attempting to define "worst conversion" (possibly with focusing on the second standard conversion sequence in user-defined conversions), it might be more consistent overall to use the rules for a succession of function arguments/parameters when comparing list-initializations.