来自 std::initializer_list 的两个构造函数之间的重载解析

Overload resolution between two constructors from std::initializer_list

在下面的程序中,struct C 有两个构造函数:一个来自 std::initializer_list<A>,另一个来自 std::initializer_list<B>。然后使用 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 );

因为只能从 int 隐式构造聚合 A,所以可以预期 C(std::initializer_list<A>) 是首选并且程序成功。在 Clang 中确实如此。

但是 GCC 抱怨:

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>)'

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

演示:https://gcc.godbolt.org/z/joz91q4ed

这里哪个编译器是正确的?

措辞可以更清楚(这不足为奇),但 GCC 和 MSVC 在这里是正确的:相关规则 ([over.ics.list]/7) 仅检查那

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

所以从 {1} 初始化 B 将是 ill-formed 的事实是无关紧要的。

有几个这样的地方,隐式转换序列比实际初始化更 自由 ,导致某些情况不明确,即使某些可能性实际上不起作用。如果程序员感到困惑并认为其中一个未遂事故实际上是 更好的 匹配,那么报告歧义就是一个特性。