std::is_nothrow_assignable 是编译期类型特征,仅检查赋值运算符是否声明为 noexcept,不验证函数体实现;对拷贝赋值默认检测,移动赋值需显式传右值引用类型,且不递归检查成员或基类异常规范。

std::is_nothrow_assignable 是编译期判断,不是运行时检测
它只看类型是否「声明」了 noexcept 的赋值运算符,不关心实际执行会不会抛异常。比如自定义类写了 T& operator=(const T&) noexcept,哪怕函数体里偷偷调了可能抛异常的代码,std::is_nothrow_assignable_v<t t></t> 依然返回 true。
常见错误现象:用它“兜底”防止运行时崩溃,结果失败——因为它根本不管实现,只信声明。
- 必须配合
static_assert或if constexpr在编译期分支,不能用于if运行时判断 - 对内置类型(如
int、double)恒为true,但对std::vector等容器,取决于其元素类型的赋值是否noexcept - 移动赋值也受此 trait 约束:
std::is_nothrow_assignable_v<t t></t>才对应移动语义安全
检测移动赋值是否无异常,得显式传右值引用类型
std::is_nothrow_assignable 默认检查的是拷贝赋值(左值 → 左值),要验证移动赋值,必须把第二个模板参数写成右值引用形式,否则白测。
使用场景:在实现移动构造或移动赋值时,想条件启用 std::move_if_noexcept 类似逻辑,或做 noexcept 规约断言。
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
std::is_nothrow_assignable_v<mytype mytype></mytype>—— 检的是拷贝赋值 - 正确写法:
std::is_nothrow_assignable_v<mytype mytype></mytype>—— 才对应移动赋值 - 若类型未定义移动赋值,该 trait 仍可能为
true(退化到拷贝赋值),需结合std::is_move_assignable_v一起确认是否真有移动语义
和 noexcept 操作符混用时,行为不等价
noexcept(expr) 是运行期常量表达式求值,会尝试实例化并检查 expr 是否可能抛异常;而 std::is_nothrow_assignable 只查函数签名,不触发实例化。
性能影响:前者可能引发模板膨胀甚至编译失败(比如 expr 含不完整类型),后者纯是类型查询,零开销。
- 例如:
noexcept(std::declval<t>() = std::declval<const t>())</const></t>和std::is_nothrow_assignable_v<t const t></t>多数情况结果一致,但前者更“严格” - 当赋值运算符是模板(如
template<typename u> T& operator=(U&&)</typename>),std::is_nothrow_assignable可能返回false(无法推导所有重载),而noexcept(...)针对具体实参能得出确定结果 - 兼容性上,
std::is_nothrow_assignable自 C++11 起可用,noexcept操作符也是 C++11,但旧编译器对复杂表达式支持不稳
实际工程中容易忽略的约束点
这个 trait 对基类/成员的异常规范有传递依赖,但不会自动递归检查——它只看直接声明的赋值运算符,不展开成员或基类。
比如一个类 A 成员含 std::vector<:string></:string>,即使 std::string 的移动赋值是 noexcept,A 的移动赋值是否 noexcept 还取决于你有没有显式加 noexcept 声明。
- 若没写
A& operator=(A&&) noexcept,即使所有成员都支持无异常移动,std::is_nothrow_assignable_v<a a></a>仍是false - 继承链中只要有一个基类的赋值运算符没标
noexcept,派生类的对应 trait 就大概率失败,除非你手动重写并标注 - 第三方库类型(如
boost::optional)是否满足,得查其文档或源码,不能默认信任
真正关键的不是知道它返回 true,而是清楚它为什么返回 true——尤其是当你依赖它做优化决策时,那个 true 很可能只来自一句没被严格执行的 noexcept 声明。








