P2786中is_replaceable的用途与语义是什么?
is_replaceable<T>的实践困惑与应用疑问 C++工作草案曾新增[编辑说明:截至2025年11月已再次移除!但为留存历史价值,本讨论将开放至2026年左右]了可替换类型(is_replaceable<T>)的定义,该定义来自提案P2786R13。P2786R13中对其的说明为:
可替换性是类型的一种语义属性,即移动赋值等价于销毁后再移动构造。
libc++正考虑添加该特性并将其作为优化判断条件,但在实践中对其实际含义产生了困惑。例如shared_ptr<int>是否属于“可替换类型”?显然,我们不能将如下移动赋值代码:
void f(shared_ptr<int>& a, shared_ptr<int>& b) { a = std::move(b); }
直接替换为销毁后构造的版本:
void f(shared_ptr<int>& a, shared_ptr<int>& b) { a.~shared_ptr(); ::new (&a) shared_ptr<int>(std::move(b)); }
因为如果b的生命周期由a控制,会引发两个问题:(1) a的引用计数会不合时宜地降至0;(2) b会在其生命周期外被访问。此前所有库类型的自移动赋值均为未定义行为*[编辑说明:现已不再是]*,但一般赋值场景(如下示例)的行为应当是明确的:
struct S { int i_; std::shared_ptr<int> p_; }; int main() { std::shared_ptr<S> oa = std::make_shared<S>(); std::shared_ptr<int> a(oa, &oa->i_); std::shared_ptr<int>& b = oa->p_; b = std::make_shared<int>(42); oa = nullptr; assert(a.use_count() == 1); assert(b.use_count() == 1); f(a, b); // 移动赋值(行为明确),或销毁后构造(引入未定义行为) printf("a.use_count() is %zu (should be 1)\n", a.use_count()); printf("*a is %d (should be 42)\n", *a); }
is_replaceable的实际含义与其预期支持的优化类型密切相关。或许其设计意图并非让开发者直接将赋值替换为销毁后构造,而是仅在is_replaceable && is_trivially_relocatable(即库厂商需求的P1144特性)同时满足时才启用优化。若如此,libc++将shared_ptr标记为is_replaceable是“安全”的,只是用户需理解该库特性与标准文档中的描述并非完全一致。
最初设想is_replaceable可用于vector::erase这类场景,但实际情况是:假设V为pmr::vector<int>,我们希望优化pmr::vector<V>::erase,但is_replaceable_v<V>的值为false。因此is_replaceable无法单独区分哪些类型适合insert/erase优化,仍需在vector中编写特殊逻辑来专门处理PMR类型。
那么库厂商该如何运用这个新特性?
内容的提问来源于stack exchange,提问作者Quuxplusone

