为什么移动和值构造函数的组合对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

本文关键字:歧义 高版本 版本 GCC Clang 而对 组合 构造函数 移动 MSVC C++17      更新时间:2024-04-28

我有以下代码

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的Clang和GCC上编译得很好(不适用于C++<17(,但抱怨

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

对于所有可用的标准,在MSVC中。

我试图通过比较MSVC、Clang和GCC来找到语言一致性的差异,也尝试了对MSVC的/permission,但还找不到任何解释。

  • 谁是对的(任何原因(以及
  • 如何在不放弃R中的R&&int i因子或将强制转换operator int()标记为显式的情况下为MSVC编译它

这里有一个相当拥挤的godbolt,包括一个可能的解决方法。

这是CWG 2327,它目前没有解决方案,但gcc和clang似乎做到了"正确的做法"问题如下:

考虑这样一个例子:

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

进入9.4[dcl.init]项目符号17.6.2:

否则,如果初始化是直接初始化,或者如果是复制初始化,其中源类型的cv不合格版本与目标类的类或派生类相同,则考虑构造函数。枚举适用的构造函数(12.2.2.4[over.match.cctor](,并通过重载解析(12.2[over.match](选择最佳构造函数。调用如此选择的构造函数来初始化对象,初始化器表达式或表达式列表作为其参数。如果没有应用构造函数,或者重载解析不明确,则初始化格式不正确。

Overload resolution选择Cat的move构造函数。根据9.4.4[dcl.init.ref]项目符号5.2.1.2,初始化构造函数的Cat&&参数会产生一个临时的。这排除了在这种情况下省略副本的可能性。

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

这里的情况类似,只是更复杂。使用任一:

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

我们有一个来自s的转换函数,它给出了一个prvalueR,这是一个比我们可以使用的任何其他路径都更好的路径——无论是通过R的move构造函数还是R(int)。但我们只是没有一条规则来说明这就是我们应该做的

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

相关文章: