来自 initalizer_list 和需要转换的值的构造函数之间的重载解析,编译器不同

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

在下面的代码中,struct A 有两个构造函数:A(int)A(std::initializer_list<char>)。然后使用 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 );

事实证明,GCC 和 Clang 在这里更喜欢来自 initializer_list 的构造函数(尽管 int 参数必须在 char 中转换)并且整个程序都被接受,但是 MSVC选择 A(int) 构造函数,使 static_assert 失败。演示:https://gcc.godbolt.org/z/qx7W417Mv

哪个编译器就在这里?

Clang 和 GCC 是对的,MSVC 有一个错误

这是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:

[...]

这些项目符号给出了与上面的例子相反的结果,并且有 是选择的实现方差。

[...]

提议的决议(2014 年 6 月):

此问题已通过问题解决方案得到解决 1467.

根据决议已通过 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.

特别是 re-ording [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.

GCC 和 Clang 等编译器从 back-ported DR 1467 开始符合早期标准(C++11 和 C++14); Clang 特别是 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 似乎没有实现此更改,即使根据更新的 C++17 标准,它不再“只是一个”DR。