Tip of the Week #231: Between Here and There: Some Minor Overlooked Algorithms

Originally posted as TotW #231 on March 7, 2024

By James Dennett

Updated 2024-09-30

Quicklink: abseil.io/tips/231

Overview

In recent C++ versions the Standard Library has added a few functions whose sole job is to supply some (specific!) point somewhere between two other points x and y: std::clamp (from C++17), and std::midpoint and std::lerp (from C++20).

Adding these function templates to the Standard Library serves two main purposes. First, it establishes common terminology (vocabulary) for these operations, that is likely to be widely recognized. Second, and particularly in the case of std::midpoint and std::lerp, it ensures the availability of high quality implementations that avoid common pitfalls.

All of these operations are constexpr, meaning that they can be used both at runtime and at compile time. The types that can be passed to them depend on the specific operation; they all support floating point types, and std::midpoint and std::clamp offer additional flexibility. Read on for the details.

std::clamp

std::clamp(x, min, max) “clamps” x to the range [min, max]. More explicitly, if x is already in the range min to max (inclusive) then std::clamp(x, min, max) returns x, and for x outside of that range std::clamp returns whichever of min or max is closest to x. This is equivalent to std::max(std::min(x, max), min) except for being a more direct way to express the intent (and much less of a puzzle for readers).

Warning: While std::clamp returns a reference, code depending on that is subtle and unusual, and would warrant a comment to alert readers. It is easy to accidentally create a dangling reference by passing a temporary to std::clamp and binding the result to a temporary:

// `std::clamp(1, 3, 4)` returns a reference to a temporary int initialized
// from `3`, which must not be used beyond the lifetime of the temporary.
// See [Tip #101](/tips/101).
const int& dangling = std::clamp(1, 3, 4);

std::clamp works for any type that can be compared with < (or with a user-supplied comparator passed to std::clamp(x, min, max, cmp).

std::midpoint

There are no surprises in what std::midpoint does: std::midpoint(x, y) returns a point halfway between x and y (rounding towards x when x and y are of an integral type).

std::midpoint(x, y) works for values x, y of any floating point or integral type (not including bool). As a bonus, std::midpoint(p, q) also works for pointers p, q into an array.

std::lerp

The lerp in std::lerp is short for “linear interpolation”, and std::lerp(x, y, t) returns a value some fraction t of the way from x to y. For example, std::lerp(x, y, 0) returns x, std::lerp(x, y, 1) returns y, and std::lerp(x, y, 0.5) can be simplified to std::midpoint(x, y).

Note: In spite of the name, std::lerp can also be used for extrapolation if we pass a value of t outside of the range [0, 1]. For example, std::lerp(100, 101, -2) evaluates to 98, and std::lerp(100, 101, +2) is 102.

std::lerp works on floating point types.

Recommendations

  1. One of the main benefits of these library functions is that they provide a common vocabulary. As always, prefer to use these standard facilities instead of writing them from scratch.
  2. Prefer std::midpoint(x, y) over std::lerp(x, y, 0.5) when applicable.
  3. Avoid declaring a reference to the result of std::clamp.

Subscribe to the Abseil Blog