Title
Comparing user-defined conversion sequences in list-initialization
Status
drafting
Section
12.2.4.2.6 [over.ics.list]
Submitter
Jim X

Created on 2021-01-11.00:00:00 last changed 1 week ago

Messages

Date: 2021-11-15.00:00:00

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 conversion worst conversion necessary to convert an element of the list to X, or if defined 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 sequence conversion 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.

Date: 2021-08-15.00:00:00

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.

Date: 2021-01-11.00:00:00

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.

History
Date User Action Args
2021-11-15 00:00:00adminsetmessages: + msg6542
2021-11-15 00:00:00adminsetmessages: + msg6541
2021-01-11 00:00:00admincreate