#include <type_traits>
#include <iostream>
template<typename T>
struct Wrapper {
T value;
operator T&() & { std::cout << "call const ref" << std::endl; return this->value; }
operator const T&() const& { std::cout << "call const ref" << std::endl; return this->value; }
operator T&&() && { std::cout << "call move" << std::endl; return std::move(this->value); }
operator const T&() const&& = delete;
operator T&&() & = delete;
operator T&&() const& = delete;
};
class A {
public:
A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
};
int main() {
Wrapper<A> b;
A bb;
bb = std::move(b);
}
我用gcc10.2编译这段代码,并得到以下错误
test.cc: In function ‘int main()’:
test.cc:24:21: error: ambiguous overload for ‘operator=’ (operand types are ‘A’ and ‘std::remove_reference<Wrapper<A>&>::type’ {aka ‘Wrapper<A>’})
24 | bb = std::move(b);
| ^
test.cc:17:12: note: candidate: ‘A& A::operator=(const A&)’
17 | A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
| ^~~~~~~~
test.cc:18:12: note: candidate: ‘A& A::operator=(A&&)’
18 | A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
| ^~~~~~~~
但我在 cppinsights.io 中使用 clang 尝试了相同的代码,并成功编译。
那么,是什么导致了 gcc 和 clang 之间的差异呢?
我该如何改变Wrapper
要解决这个问题?
https://godbolt.org/z/37dPqafGK https://godbolt.org/z/37dPqafGK
引用的文本来自 C++20 标准,但链接指向 N4861。
首先让我们最小化给定的代码。如果删除其中三个转换函数,仅留下:,GCC 11.2 和 Clang 12.0.1 的结果如问题中所示(Clang 接受,GCC 拒绝)
operator const T&() const&; // not deleted
operator T&&() &&; // not deleted
operator const T&() const&& = delete;
由此我们可以推断,Clang 只接受代码,因为在尝试形成隐式转换序列时,该序列将启用A& A::operator=(const A&)
被调用时,它选择已删除的Wrapper<A>::operator const T&() const&&
执行转换为const A&
。因此,形成隐式转换序列fails, and A& A::operator=(const A&)
不可行,只剩下另一个候选人了。
GCC 和 Clang 之间的差异似乎与CWG 第 2525 期 https://wg21.link/CWG2525。问题如下:注释1的意图似乎是[over.ics.best.general]/2(N4861 中的[over.ics.best]/2) https://timsong-cpp.github.io/cppwp/n4861/over.best.ics#2.note-1是如果一个隐式转换序列涉及到删除的函数,则仍然认为该隐式转换序列已形成,并且只有需要该隐式转换序列的候选者被选择时,then该程序格式错误,因为它需要调用已删除的函数。但规范性的措辞似乎并没有体现这一意图。
GCC 和 Clang 同意该声明const A& t = std::move(b);
格式错误,因为它调用了已删除的函数Wrapper<A>::operator const A&() const&&
。因此,Clang 似乎遵循当前存在的规范措辞:因为初始化const A& t = std::move(b)
将是格式错误的,因此std::move(b)
is 不能隐式转换为 const A&
under [conv]/3 https://timsong-cpp.github.io/cppwp/n4861/conv#3,因此使复制赋值运算符成为可行候选者所需的隐式转换序列不存在,并且根据 Clang,只有移动赋值运算符是可行的。但 GCC 正在遵循注释似乎告诉我们的内容:忽略(在重载决策阶段)隐式转换序列之一包含已删除函数的事实。这最终导致GCC无法决定是调用复制赋值运算符还是移动赋值运算符。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)