9

In following program, struct C has two constructors : one from std::initializer_list<A> and the other from std::initializer_list<B>. Then an object of the struct is created with C{{1}}:

#include <initializer_list>

struct A {
    int i;
};

struct B {
    constexpr explicit B(int) {}
};

struct C {
    int v;
    constexpr C(std::initializer_list<A>) : v(1) {}
    constexpr C(std::initializer_list<B>) : v(2) {}
};

static_assert( C{{1}}.v == 1 );

Since only aggregate A can be implicitly constructed from int, one could expect that C(std::initializer_list<A>) is preferred and the program succeeds. And indeed it does in Clang.

However GCC complains:

error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
note: candidate: 'constexpr C::C(std::initializer_list<B>)'
note: candidate: 'constexpr C::C(std::initializer_list<A>)'

and so does MSVC:

error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'C'
note: No constructor could take the source type, or constructor overload resolution was ambiguous

Demo: https://gcc.godbolt.org/z/joz91q4ed

Which compiler is correct here?

Fedor
  • 17,146
  • 13
  • 40
  • 131

1 Answers1

8

The wording could be clearer (which is unsurprising), but GCC and MSVC are correct here: the relevant rule ([over.ics.list]/7) checks only that

overload resolution […] chooses a single best constructor […] to perform the initialization of an object of type X from the argument initializer list

so the fact that the initialization of B from {1} would be ill-formed is irrelevant.

There are several such places where implicit conversion sequences are more liberal than actual initialization, causing certain cases to be ambiguous even though some of the possibilities wouldn’t actually work. If the programmer was confused and thought one of those near misses was actually a better match, it’s a feature that the ambiguity is reported.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Thanks. If I understood this *the fact that the initialization of B from {1} would be ill-formed is irrelevant* correctly, then even removing the constructor `B::B(int)` will keep ambiguity, but now all compilers select `A`: https://gcc.godbolt.org/z/3n63nTfrW – Fedor Feb 13 '22 at 09:28
  • 1
    @Fedor Without `B::B(int)` the condition that «overload resolution … chooses a single best constructor» is not satisfied, [over.ics.list]/7 does not apply. – Language Lawyer Feb 13 '22 at 11:21
  • Thanks, and one more question. If one adds constructor from `int` to `A`, then MSVC starts accepting the code as well: https://gcc.godbolt.org/z/zz4b4a6j3. Is it wrong now? – Fedor Feb 14 '22 at 11:54
  • 1
    @Fedor: It seems that way, although I can’t easily explain why MSVC would think that initializing an `A` became a *better* conversion with a constructor. – Davis Herring Feb 14 '22 at 22:15