我想知道,标准类型的赋值运算符没有左值引用限定是否有原因?他们都不是。
因此,我们可以这样写:
std::string{} = "42";
std::string s = "hello " + std::string{"world"} = "oops!";
std::vector<int> v = { 1,2,3 };
std::move(v) = { 4,5,6 };
如果赋值运算符是左值引用限定的,所有这些示例都将无法编译。
是因为要修改的东西很多(但后来就这样了)而没有人写提案?我不认为人们会写这样的代码,但库不应该被设计成甚至不允许这样做吗?
您的建议是于 2009 年提出的,并最终在当年法兰克福被拒绝,原因是“对向后兼容性的担忧”。
这将是一个突破性的改变,我们不喜欢这些。
现有的禁止分配给内置类型右值的禁令无论如何都只有有限的实际价值,因此潜在破坏现有代码的成本几乎肯定被认为“不值得”。
如果我们有一个干净的石板,图书馆会以这种方式设计吗?也许吧。
C++23 在这个问题上已经取得了一些进展。 C++11 在库中编纂了预先存在的最佳实践,即“
const
意味着线程安全”。
C++23 最终在库中编写了预先存在的最佳实践,即“const
表示可在右值上调用”——具体来说,“const-assignable 表示代理引用。”
诸如
tuple<int, int>
之类的类型继续具有普通的tuple& operator=(const tuple&)
。但是像 tuple<int&, int&>
这样的类型——std::tie
的返回值——现在也有 const tuple& operator=(const tuple&) const
。这样就可以在 C++23 中工作了:
int i=1, j=2;
const auto tup = std::tie(i, j); // Note `const` here!
tup = std::make_tuple(j, i); // Proxy references are const-assignable
这意味着我们最终可以区分那些应该是右值可分配的类型(即像
tuple<int&>
这样的代理引用类型)和不是应该是右值可分配的类型(即所有其他类型) ).
并且that意味着每当我们看到有人试图分配给非代理引用类类型的非常量右值时,我们终于可以发出编译器警告!我已经在我个人的 Clang 分支中实现了诊断;它只需要有人(不是我)自愿将其上游到 Clang 主干和/或 GCC 中。
-Wassign-to-class-rvalue
”(2024-12-13)这是您的示例 (Godbolt),显示了新的诊断:
<source>:5:17: warning: possibly unintended assignment to an rvalue of
type 'std::string' (aka 'basic_string<char>') [-Wassign-to-class-rvalue]
5 | std::string{} = "42";
| ~~~~~~~~~~~~~ ^
.../include/c++/v1/string:1270:69: note: declared here
1270 | _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(const value_type* __s) {
| ^
换句话说,我们今天就可以得到您想要的编译器行为,而无需更改语言规范。编译器可以检测到这种bug,对于想要检测它的人(应该是几乎所有人);对于拥有十亿行代码库的人来说,实际上不会“破坏旧代码”,并且不会因分配的特殊情况而“使语言规范复杂化”。
该博客文章更详细地介绍了复杂的极端情况。例如,您不仅关心 operator=
,还关心
operator+=
和后缀
operator++
...那么前缀 operator++
呢,我们是否也应该为其添加一个引用限定符?... 。 等等。将该功能转移到一个简单的“编译器诊断”中可以解决所有这些棘手的问题;编译器供应商可以迭代其诊断的质量,而无需与语言标准化过程交互。