每周技巧 #231:这里和那里之间:几个容易忽视的小算法

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #231: Between Here and There: Some Minor Overlooked Algorithms

原文最初作为 TotW #231 发布于 2024 年 3 月 7 日。

作者:James Dennett

更新于 2024 年 9 月 30 日。

快捷链接:abseil.io/tips/231

概览

在较新的 C++ 版本中,标准库添加了几个函数,它们唯一的工作是在两个点 xy 之间的某处提供某个(特定的!)点:std::clamp(C++17 起),以及 std::midpointstd::lerp(C++20 起)。

把这些函数模板加入标准库有两个主要目的。第一,它为这些操作建立了可能被广泛识别的通用术语(词汇)。第二,特别是在 std::midpointstd::lerp 的情况下,它确保我们可以使用高质量实现,来避免常见陷阱

这些操作都是 constexpr,意味着它们可以在运行期和编译期使用。能传给它们的类型取决于具体操作;它们都支持浮点类型,而 std::midpointstd::clamp 提供了额外灵活性。细节如下。

std::clamp

std::clamp(x, min, max) 会把 x “夹紧”到 [min, max] 范围内。更明确地说,如果 x 已经在 minmax 的范围内(含端点),那么 std::clamp(x, min, max) 返回 x;如果 x 在范围之外,std::clamp 返回 minmax 中更接近 x 的那个。这等价于 std::max(std::min(x, max), min),但它是更直接表达意图的方式(也不那么考验读者)。

警告:虽然 std::clamp 返回一个引用,但依赖这一点的代码微妙且不常见,应该添加注释提醒读者。向 std::clamp 传入临时对象并把结果绑定到引用,很容易意外创建悬垂引用:

1
2
3
// `std::clamp(1, 3, 4)` 返回一个引用,指向由 `3` 初始化的临时 int,
// 不能在该临时对象生命周期结束后使用它。见技巧 #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) 返回 xy 中间的点(当 xy 是整数类型时,向 x 方向舍入)。

std::midpoint(x, y) 适用于任意浮点或整数类型(不包括 bool)的值 xy。额外 bonus 是,std::midpoint(p, q) 也适用于数组中的指针 pq

std::lerp

std::lerp 中的 lerp 是 “linear interpolation”(线性插值)的缩写,std::lerp(x, y, t) 返回从 xy 路程中比例为 t 的某个值。例如,std::lerp(x, y, 0) 返回 xstd::lerp(x, y, 1) 返回 ystd::lerp(x, y, 0.5) 可以简化为 std::midpoint(x, y)

注意:尽管名字如此,如果传入范围 [0, 1] 之外的 tstd::lerp 也可用于外推。例如,std::lerp(100, 101, -2) 求值为 98,std::lerp(100, 101, +2) 为 102。

std::lerp 适用于浮点类型。

建议

  1. 这些库函数的主要好处之一是提供通用词汇。一如既往,优先使用这些标准设施,而不是从头编写。
  2. 适用时,优先使用 std::midpoint(x, y),而不是 std::lerp(x, y, 0.5)
  3. 避免声明对 std::clamp 结果的引用。

每周技巧 #229:用于模板元编程的分级重载

上一节

每周技巧 #232:变量声明中何时使用 `auto`

下一节