为何不同C++版本及编译器对用户定义右值引用转换的重载决议不同?
GCC与Clang在C++17下的重载决议差异问题
异常现象
当参数通过用户定义转换运算符隐式转换时,GCC与Clang在不同C版本下会选择不同的重载构造函数:仅在Clang搭配C17及更高版本时,拷贝构造函数会被优先于移动构造函数选择。
测试代码
#include <cstdio> #include <utility> #include <type_traits> template <typename T> class foo { T& t_; public: foo(T& t) : t_(t) {} operator T() const & { return t_; } operator T&&() && { return std::move(t_); } }; class bar { int val_; public: bar(int v) : val_(v) {} bar(const bar& b) : val_(b.val_) { printf("copy constructed\n"); } bar& operator=(const bar& b) { printf("copy assigned\n"); val_ = b.val_; return *this; } bar(bar&& mv) : val_(mv.val_) { printf("move constructed\n"); mv.val_ = -1; } bar& operator=(bar&& mv) { printf("move assigned\n"); val_ = mv.val_; mv.val_ = -1; return *this; } }; int main() { bar v(1); foo<bar> f(v); bar v2(std::move(f)); }
编译测试结果
⟩ clang++-15 -std=c++17 test.cpp && ./a.out copy constructed ⟩ clang++-15 -std=c++14 test.cpp && ./a.out move constructed ⟩ g++ -std=c++14 test.cpp && ./a.out move constructed ⟩ g++ -std=c++17 test.cpp && ./a.out move constructed
编译器版本信息
⟩ g++ --version g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ⟩ clang++-15 --version Ubuntu clang version 15.0.4-++20221102053308+5c68a1cb1231-1~exp1~20221102053355.92 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin
原因分析
这个差异源于C++17对重载决议规则的调整,以及不同编译器的实现细节:
- C++14及更早版本:编译器优先选择与对象值类别匹配的转换函数。
std::move(f)是右值,因此右值限定的operator T&&() &&会被优先选中,其返回的bar&&直接匹配bar(bar&&)移动构造函数,最终输出move constructed。 - C++17规则变化:C++17调整了隐式转换序列的优先级判定逻辑,当存在多个可行转换路径时,转换到值类型(
operator T()返回bar)的序列被视为更直接的匹配。此时Clang严格遵循该规则,选择通过operator T() const &转换得到临时bar对象,再将其绑定到const bar&参数调用拷贝构造函数。 - 编译器实现差异:GCC在C17版本中仍保留了C14的优先级逻辑,优先选择与右值对象匹配的
operator T&&() &&转换函数,因此仍调用移动构造函数。
你的代码并未违反C规则,该现象属于不同编译器对C17重载决议细节的实现差异。
内容的提问来源于stack exchange,提问作者s417-lama
相关产品推荐
相关产品推荐

