作为TotW#231最初发表再2024年3月7日
作者:James Dennett
概述
在最近的 C++ 版本中,标准库新增了一些专用函数,用于在两个点 x 和 y 之间提供某个(特定的!)点:std::clamp(C++17 引入),以及 std::midpoint 和 std::lerp(C++20 引入)。
将这些函数模板添加到标准库有两个主要目的:
建立通用术语(词汇),便于广泛识别这些操作的含义。
提供高质量实现,尤其是 std::midpoint 和 std::lerp,以避免常见陷阱。
所有这些操作都是 constexpr 的,这意味着它们既可在运行时使用,也可在编译时使用。可传递给这些函数的类型取决于具体操作;它们都支持浮点类型,同时 std::midpoint 和 std::clamp 还具有额外的灵活性。以下是详细信息。
std::clamp
std::clamp(x, min, max) 将 x 限制在范围 [min, max] 内。更具体地说,如果 x 已在 min 到 max 范围内(包含边界),则 std::clamp(x, min, max) 返回 x;如果 x 超出该范围,则返回 min 或 max 中距离 x 最近的值。这相当于 std::max(std::min(x, max), min),但表达意图更直接,也更易于读者理解。
注意:尽管 std::clamp 返回的是引用,但依赖这一特性是微妙且不常见的,代码中应添加注释以提醒读者。如果将临时值传递给 std::clamp 并将结果绑定到引用,很容易意外创建悬空引用:
// `std::clamp(1, 3, 4)` 返回对从 `3` 初始化的临时变量的引用,
// 该引用在临时变量生命周期结束后不得使用。
// 参见 [技巧 #101](/tips/101)。
const int& dangling = std::clamp(1, 3, 4);
std::clamp 适用于任何可以用 < 比较的类型(或通过用户提供的比较器 std::clamp(x, min, max, cmp))。
std::midpoint
std::midpoint 的功能没有意外:std::midpoint(x, y) 返回 x 和 y 的中点(对于整型,向 x 的方向舍入)。
std::midpoint(x, y) 适用于任何浮点或整型值(不包括 bool)。此外,std::midpoint(p, q) 也适用于指向同一数组的指针 p 和 q。
std::lerp
std::lerp 中的 lerp 是“线性插值”(linear interpolation)的缩写,std::lerp(x, y, t) 返回从 x 到 y 的某个分数 t 的值。例如:
std::lerp(x, y, 0) 返回 x,
std::lerp(x, y, 1) 返回 y,
std::lerp(x, y, 0.5) 可简化为 std::midpoint(x, y)。
注意:尽管名称中包含“插值”,但若传入 t 的值超出范围 [0, 1],std::lerp 也可用于外推。例如:
std::lerp(100, 101, -2) 的结果为 98,
std::lerp(100, 101, +2) 的结果为 102。
std::lerp 适用于浮点类型。
建议
这些库函数的主要优势之一是提供了通用术语。因此,优先使用这些标准功能,而不是重新实现。
在适用时,优先使用 std::midpoint(x, y) 而非 std::lerp(x, y, 0.5)。
避免将 std::clamp 的结果声明为引用。