Overload resolution between constructors from initalizer_list and from a value requiring conversion, compilers diverge
Asked Answered
M

1

5

In the following code, struct A has two constructors: A(int) and A(std::initializer_list<char>). Then an object of the struct is created with A({0}):

#include <initializer_list>

struct A {
    int a;
    constexpr A(int) { a = 1; } 
    constexpr A(std::initializer_list<char>) { a = 2; }
};

static_assert( A({0}).a == 2 );

It turns out that GCC and Clang prefer here the constructor from initializer_list (despite int argument has to be converted in char) and the whole program is accepted, but MSVC selects A(int) constructor, failing the static_assert. Demo: https://gcc.godbolt.org/z/qx7W417Mv

Which compiler is right here?

Multipartite answered 3/2, 2022 at 7:25 Comment(6)
Brace-enclosed lists (even with only a single element) should be considered an std::initializer_list.Thymus
Similar demo: godbolt.org/z/fnGTdGscK. If the template argument of std::initializer_list is changed to int, then MSVC agrees with GCC.Bussy
Possibly relevant standard section with a similar example: eel.is/c++draft/over#ics.rank-3.1.Bussy
Relevant (possibly even duplicate; but with user-defined conversion): Wrong overload called when constructing from initializer_list inside parenthesesBussy
Notice there is also copy constructor in overload set (so interpreted as A(A{0})).Jettiejettison
@DanielLangr, thanks for the reference. It looks similar, but there GCC is wrong, diverging from Clang and MSVC due to gcc.gnu.org/bugzilla/show_bug.cgi?id=99273Multipartite
W
7

Clang & GCC are right, MSVC has a bug

This is CWG 1589:

1589. Ambiguous ranking of list-initialization sequences

Section: 16.3.3.2 [over.ics.rank]
Status: CD4
Submitter: Johannes Schaub
Date: 2012-11-21

[Moved to DR at the November, 2014 meeting.]

The interpretation of the following example is unclear in the current wording:

void f(long);
void f(initializer_list<int>);
int main() { f({1L});

The problem is that a list-initialization sequence can also be a standard conversion sequence, depending on the types of the elements and the type of the parameter, so more than one bullet in the list in 16.3.3.2 [over.ics.rank] paragraph 3 applies:

[...]

These bullets give opposite results for the example above, and there is implementation variance in which is selected.

[...]

Proposed resolution (June, 2014):

This issue is resolved by the resolution of issue 1467.

Which as per the resolution was resolved via CWG 1467:

1467. List-initialization of aggregate from same-type object

[...]

Proposed resolution (June, 2014):

[...]

  1. Move the final bullet of 16.3.3.2 [over.ics.rank] paragraph 3 to the beginning of the list and change it as follows:

[...]

even if one of the other rules in this paragraph would otherwise apply. [Example: ... — end example]

This resolution also resolves issues 1490, 1589, 1631, 1756, and 1758.

Particularly by re-ording the [over.ics.rank]/3 such that [over.ics.rank]/3.1 now applies for the OP's case, and takes precedence over the other rules of [over.ics.rank]/3:

List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if

  • (3.1.1) L1 converts to std​::​initializer_­list<X> for some X and L2 does not, or, if not that
  • (3.1.2) [...]

even if one of the other rules in this paragraph would otherwise apply.

Compilers such as GCC and Clang has since back-ported DR 1467 to earlier standards (C++11 and C++14); Clang specifically as of Clang 3.7, and we can verify the CWG 1589 connection to OP's example as it is rejected by Clang 3.6 but accepted by Clang 3.7 (DEMO).

MSVC, on the other, has seemingly has not implemented this change even as per the updated C++17 standard where it is no longer "just a" DR.

Wrac answered 3/2, 2022 at 12:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.