转换和移动 ctor 导致对 Clang 和 GCC 4.9.2 的模棱两可的要求
Conversion and move ctor leads to ambiguous call for Clang and GCC 4.9.2
我对 C++11 中的以下转换问题有点困惑。给定此代码:
#include <utility>
struct State {
State(State const& state) = default;
State(State&& state) = default;
State() = default;
int x;
};
template<typename T>
struct Wrapper {
T x;
Wrapper() = default;
operator T const&() const& { return x; }
// version which also works with GCC 4.9.2:
// operator T&&() && { return std::move(x); }
// version which does not work with GCC 4.9.2:
operator T() && { return std::move(x); }
};
int main() {
Wrapper<State> x;
State y(std::move(x));
}
Godbolt 链接到与 Clang 的失败编译
在上面的表单中,从版本 5.1 开始的 g++ 以及 ICPC 版本 16 和 17 编译代码。如果我取消注释T&&
转换运算符并在当前使用的第二个运算符中注释:
operator T&&() && { return std::move(x); }
// version which does not work with GCC 4.9.2:
// operator T() && { return std::move(x); }
然后 GCC 4.9 也编译。否则,它会抱怨:
foo.cpp:23:23: error: call of overloaded ‘State(std::remove_reference<Wrapper<State>&>::type)’ is ambiguous
State y(std::move(x));
^
foo.cpp:23:23: note: candidates are:
foo.cpp:5:3: note: constexpr State::State(State&&)
State(State&& state) = default;
^
foo.cpp:4:3: note: constexpr State::State(const State&)
State(State const& state) = default;
但是,clang 从不编译代码,同样抱怨对State
构造函数的模棱两可的调用。
这个,我不明白。考虑到std::move(x)
,我希望有一个类型Wrapper<State>
的右值。那么,转换运算符T&&() &&
不应该明显优于T const&() const&
运算符吗?鉴于此,State
的右值引用构造函数不应该用于从转换的右值引用返回值构造y
吗?
有人可以向我解释歧义,理想情况下还可以解释 Clang 或 GCC(如果是,在哪个版本中)是否正确,以及实现从包装器移动到状态对象的最佳方法是什么?
值得注意的是,最新版本的 Clang 和 GCC 不再相互不同意。在 C++11 模式下,过载分辨率不明确。神螺栓链接
State
初始化有两个候选对象:复制构造函数和移动构造函数。
为了调用复制构造函数,编译器需要找到从std::move(x)
(类型为Wrapper<State>
的xvalue)到const State&
的隐式转换序列。从类类型初始化引用时,返回兼容引用类型的该类的转换函数优先于返回引用可以绑定到的临时的转换函数。见C++11 [dcl.init.ref]/5(强调我的):
对类型">cv1
T1
"的引用由类型">cv2T2
"的表达式初始化,如下所示:
如果引用是左值引用和初始值设定项表达式
- 是一个左值(但不是位字段),并且">cv1
T1
"与">cv2T2
"兼容,或- 具有类类型(即,
T2
是类类型),其中T1
与T2
无关,并且可以隐式转换为类型为 "cv3T3
" 的左值,其中 "cv1T1
" 与 ">cv3T3
" 引用兼容(此转换是通过枚举适用的转换函数 (13.3.1.6) 并通过重载分辨率 (13.3) 选择最佳转换函数来选择的),然后,在第一种情况下,引用绑定到初始值设定项表达式 lvalue,并绑定到 lvalue 结果 在第二种情况下的转换(或者,在任一情况下,转换为适当的基类子对象 对象)。[...]
否则, [...]
因此,复制构造函数的隐式转换序列是调用operator T const&
并将State const&
参数绑定到该调用的结果。
对于移动构造函数,唯一的可能性是调用operator T
。
那么问题就变成了这两个隐式转换序列中哪一个更好。调用operator T const&
,然后将State const&
绑定到该调用的结果?或者调用operator T
,然后将State&&
绑定到该调用的结果?
比较两个用户定义的转换序列的规则取自 ([over.ics.rank]/3):
用户定义的转换序列
U1
如果它们包含相同的用户定义的转换函数或构造函数或聚合,则U2
是比其他用户定义的转换序列更好的转换序列 初始化和第二标准转换序列U1
优于第二标准U2
的转换顺序。
然而,在这种情况下,涉及两个不同的用户定义转换函数(operator T const&
与operator T
),因此两个用户定义的转换序列是无可比拟的。重载分辨率确实模棱两可,就像 Clang 16 和 GCC 12.2 所说的那样(在 C++11 模式下)。
请注意,Clang 和 GCC 也同意,当您使用operator T&&
而不是operator T
时,代码仍然不明确(在 C++11 模式下)。在这种情况下,在确定State
move 构造函数的隐式转换序列时,State&&
参数只能绑定到调用operator T&&
的结果。这仍然是一个与State const&
参数使用的转换函数不同的转换函数,因此所涉及的两个隐式转换序列仍然无法比拟。
当你进入C++17模式时,事情变得有趣。在这种情况下,最新版本的Clang和GCC都更愿意调用operator T
。这是因为它们具有实际上不在标准中的特殊过载解决规则。有关此行为的说明,请参阅 P2828R0。但是,如果接受P2828R0中提出的方向,那么此代码仍将模棱两可(也许Clang和GCC将不得不改变他们的行为),所以我建议不要依赖它。
我想您可能真正想要的是使对象表达式可以绑定到右值引用时永远不会选择const &
限定的转换函数。我不知道在当前C++中有什么方法可以做到这一点,但是您可以使用显式对象参数在 C++23 中做到这一点:
template <typename Self>
operator T const& (this Self&& self)
requires (!std::convertible_to<Self&&, Wrapper&&>) {
return self.x;
}
- 奇怪的结构&GCC&clang(void*返回类型)
- 数据成员SFINAE的C++17测试:gcc vs clang
- 当我编译webrtc服务器时,Windows上只支持clang-cl
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 我可以将一个用clang c++11编译的对象与另一个用c++17编译的对象链接起来吗
- Clang bug?使用指针作为模板参数
- clang整洁10忽略了我的NOLINT命令
- 如何防止clang格式在流运算符调用之间添加换行符<<
- 在clang++预处理器中确定gcc工具链版本
- 为什么 Clang 不允许"and"作为函数名称?
- 带有 -stdlib=libc++ 的 clang++ 9.0.1 找不到<optional>
- clang格式:宏的缩进
- CLANG 编译器 说:变量"PTR"可能未初始化
- clang格式:禁用排序包含
- C++17 年与 Clang 的模棱两可的部分专业化
- 是可调用和模棱两可的调用:g++ 或 clang 中的错误
- GCC称将功能与多个继承过载时,称其为模棱两可,但Clang和MSVC没有
- MSVC发现这种方法调用模棱两可,而Clang / GCC则不然吗?
- 转换和移动 ctor 导致对 Clang 和 GCC 4.9.2 的模棱两可的要求
- 使用C 11的clang分类具有模棱两可的编译器行为