Overload resolution between two constructors from std::initializer_list
Asked Answered
C

1

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?

Copyhold answered 12/2, 2022 at 19:42 Comment(0)
F
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.

Foregoing answered 12/2, 2022 at 23:30 Comment(4)
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: gcc.godbolt.org/z/3n63nTfrWCopyhold
@Copyhold Without B::B(int) the condition that «overload resolution … chooses a single best constructor» is not satisfied, [over.ics.list]/7 does not apply.Digester
Thanks, and one more question. If one adds constructor from int to A, then MSVC starts accepting the code as well: gcc.godbolt.org/z/zz4b4a6j3. Is it wrong now?Copyhold
@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.Foregoing

© 2022 - 2024 — McMap. All rights reserved.