std::midpoint 能防整型/指针溢出,因其内部避开 a + b、对同号整型用 a + (b - a) / 2、对指针强制同数组且检查差值范围,而 (a + b) / 2 在 int 上触发未定义行为。

直接用 std::midpoint 替换 (a + b) / 2 并不能自动防溢出——它只在类型匹配、C++20 启用、且参数语义合法的前提下才真正起效。手写 a + (b - a) / 2 或位运算也能防整型加法溢出,但 std::midpoint 的价值在于统一处理整型/指针、编译期拦截非法组合、并规避无符号回绕和负数右移陷阱。
为什么 (a + b) / 2 在 int 上会崩,而 std::midpoint 不会
有符号整数加法溢出是未定义行为(UB),编译器可随意优化或生成错误代码。std::midpoint 内部不依赖 a + b,而是对整型做分支:同号时等价于 a + (b - a) / 2(差值一定不溢出),异号时走安全除法路径;对指针则强制要求同数组,用 begin + (end - begin) / 2 形式,但确保 end - begin 不超 ptrdiff_t 表示范围。
-
INT_MAX + 1是 UB,std::midpoint(INT_MAX, 1)返回INT_MAX / 2(正确) - 手动写
a + (b - a) / 2对无符号类型(如size_t)在b 时会回绕,结果错;std::midpoint编译不过,提前暴露问题 - 负数右移(如
(a ^ b) >> 1)是“实现定义”,std::midpoint保证数学语义一致(向下取整)
哪些场景必须用 std::midpoint,不能手写
唯一不可替代的用途是同数组内指针中点计算——这是标准唯一明确要求规避指针算术溢出的场合。
- 大数组边界:比如
int arr[SIZE_MAX / sizeof(int) + 1],end接近指针最大值,end - begin可能超ptrdiff_t范围,手写begin + (end - begin) / 2是未定义行为;std::midpoint(begin, end)要求实现保证安全 - 类型强约束:只接受
T*和T*,拒绝const T*与T*混用、void*、迭代器(如std::vector::iterator),避免误用 - 编译期卡死非法输入:比如
std::midpoint(p, q)中p和q不指向同一数组,某些实现会触发 SFINAE 失败或静态断言
std::midpoint 不能用的典型情况
它不是通用平均函数,很多看似合理调用实则编译失败或语义错位。
- 类型不匹配:
std::midpoint(1, 1L)、std::midpoint(1U, 2)直接编译错误;必须显式统一,如std::midpoint(static_cast(1), 1LL) - 浮点数无实际增益:
std::midpoint(1e30f, 1.0f)底层仍调用(a + b) / 2,可能溢出为inf;此时应改用std::fma(a, 0.5f, b * 0.5f) - 混合 size_t / ptrdiff_t:
size_t是无符号,ptrdiff_t是有符号,二者不能混传;二分查找中若索引用size_t,就得全用size_t,别试图“更安全”地转成ptrdiff_t - C++17 及更早版本:头文件
里根本没有这个符号,-std=c++20也救不了 GCC 9 或 MSVC 2017
替换老代码时最容易漏掉的三件事
把所有 (low + high) / 2 换成 std::midpoint(low, high) 是危险的起点——你得先确认上下文是否允许。
- 检查 low/high 是否真为同一类型:二分查找里常混用
int索引和size_t容器大小,直接替换会编译失败 - 确认区间非空且索引有效:
std::midpoint不校验low ,也不管high是否越界;它只保证“算得对”,不保证“算得有意义” - 留意负数舍入方向:
std::midpoint(-3, 2)返回-1(向下取整),而旧代码若依赖向零取整(如(-3 + 2) / 2 == 0在某些平台表现不同),行为会变
最易被忽略的是:它只解决“中点怎么算不崩”,不解决“中点该不该算”。比如二分查找中 low > high 已是逻辑错误,std::midpoint 依然会返回一个数学上合法但业务上无意义的值——你得在调用前兜住边界条件,而不是指望它替你做判断。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











