Search code examples
c++language-lawyerinitializer-listoverload-resolution

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


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?


Solution

  • 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.