为什么这段在函数参数中使用指定初始值设定项的代码在删除一个函数时会从模棱两可变成无法编译?

Why does this code using designated initializers in function parameters goes from ambiguous to not compiling when removing one function?

考虑以下代码:

struct A{
    int x;
    int y;
};
struct B{
    int y;
    int x;
};

void func (A){
}
void func (B){
}

int main()
{
    func({.y=1,.x=1});
}

出于某种原因,clang 和 gcc 都认为此代码不明确,即使已知指定初始化程序中的顺序必须与结构中的顺序匹配,尽管出于某种原因 clang 允许它并且只是发出警告:

ISO C++ requires field designators to be specified in declaration order; field 'y' will be initialized after field 'x' [-Wreorder-init-list]

好戏开始了: 如果我注释掉 func(B) 代码会从模棱两可变为无法编译。

这就是我认为超级奇怪的地方。这背后有什么逻辑吗?

如果我的困惑原因不清楚:

如果我们在代码中同时具有 func(A)func(B) gcc 和 clang 都认为 func({.y=1,.x=1}) 不明确,但是如果我从源代码中删除 func(B) 那么它会给出一个错误(或发出 clang 时的警告)。换句话说,我们通过删除 1 个选项从 2 个选项变为 0 个选项。

godbolt

规则在[over.ics.list]/2:

If the initializer list is a designated-initializer-list, a conversion is only possible if the parameter has an aggregate type that can be initialized from the initializer list according to the rules for aggregate initialization ([dcl.init.aggr]), in which case the implicit conversion sequence is a user-defined conversion sequence whose second standard conversion sequence is an identity conversion.

[Note 1: Aggregate initialization does not require that the members are declared in designation order. If, after overload resolution, the order does not match for the selected overload, the initialization of the parameter will be ill-formed ([dcl.init.list]).

[Example 1:

struct A { int x, y; };
struct B { int y, x; };

void f(A a, int);               // #1
void f(B b, ...);               // #2

void g(A a);                    // #3
void g(B b);                    // #4

void h() {
  f({.x = 1, .y = 2}, 0);       // OK; calls #1
  f({.y = 2, .x = 1}, 0);       // error: selects #1, initialization of a fails
                                // due to non-matching member order ([dcl.init.list])
  g({.x = 1, .y = 2});          // error: ambiguous between #3 and #4
}

end example]

end note]

基本上,指定者必须命名成员的规则在[dcl.init.aggr]中(所以它很重要你是否可以形成一个转换序列)但是指定者必须按顺序的规则在[dcl.init.list](所以这就像一个后来的需求发生了,并不影响是否可以形成转换序列 - 所以它不是“sfinae-friendly”)。

换句话说,这很好:

struct A { int a; };
struct B { int b; };
​
void f(A);
void f(B);
​
void call_f() {
    f({.a=1}); // ok, calls f(A)
}

因为你不能根据聚合初始化规则从{.a=1}初始化一个B

但是你的例子(和上面引用的那个)是模棱两可的,因为两者都满足聚合初始化规则但后来失败了。