为什么这种移动和值构造函数的组合对于 MSVC 不明确,但对于 C++17 及更高版本中的 Clang 和 GCC 却不明确

Why is this combination of move and value constructor ambigous for MSVC but not for Clang and GCC in C++17 and above

我有以下代码

struct R {
    int i_ = 8;
    R() = default;
    R(R const& o) = default;
    R(R&& o) = default;
    R(int i) : i_(i) {}
    operator int() const { return i_; }
};
struct S {
    R i_;
    operator R() const { return i_; }
    operator int() const { return static_cast<int>(i_); }
};
int main() {
    S s;
    R r0(s);
    R r = static_cast<R>(s);
    float af[] = {1,2,3};
    float f1 = af[s];
    float f2 = af[r];
}

在 C++17 和 C++20(不适用于 C++ < 17)的 Clang 和 GCC 上编译良好,但抱怨

'R::R': ambiguous call to overloaded function
note: could be 'R::R(R &&)'
note: or       'R::R(int)'

在 MSVC 中适用于所有可用标准。

我试图通过比较 MSVC MSVC, Clang and GCC and also tried /permissive- 来找出语言一致性的差异,但还找不到任何解释。

这里 a rather crowded godbolt 包括一个可能的解决方法。

这是 CWG 2327,它目前没有解决方案,但 gcc 和 clang 似乎在做“正确的事情”。问题内容如下:

Consider an example like:

 struct Cat {};
 struct Dog { operator Cat(); };

 Dog d;
 Cat c(d);

This goes to 9.4 [dcl.init] bullet 17.6.2:

Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (12.2.2.4 [over.match.ctor]), and the best one is chosen through overload resolution (12.2 [over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

重载决议选择Cat的移动构造函数。根据 9.4.4 [dcl.init.ref] 项目符号 5.2.1.2,初始化构造函数的 Cat&& 参数会产生一个临时值。这排除了这种情况下复制省略的可能性。

这似乎是对保证复制省略的措辞更改的疏忽。在这种情况下,我们大概应该同时考虑构造函数和转换函数,就像我们对复制初始化所做的那样,但我们需要确保这不会引入任何新问题或歧义。

这里的情况类似,只是更复杂。有:

R r0(s);
R r = static_cast<R>(s);

我们有一个来自 s 的转换函数,它给出了一个 prvalue R,这是一个比我们可以使用的任何其他路径都更好的路径——无论是通过 R的移动构造函数或R(int)。但我们只是没有规定那是我们应该做的。

gcc 和 clang 似乎在 C++17 或更高版本(我们保证复制省略)上实现了所需的行为,尽管没有任何关于所需行为的措辞。另一方面,msvc 似乎遵循指定的规则(我认为确实将这种情况指定为模棱两可)。