std::clamp(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ‘‘ std::clamp

okay so bestie, you've landed on the std::clamp(3) page from the C++ Standard Library manual (section 3, naturally πŸ’…). Buckle up, because this little function is about to become your new fave. ✨

🌸 NAME

std::clamp β€” that's it, that's the name. std::clamp. Short, sweet, and it ate. πŸŽ€

πŸ¦‹ Synopsis

Defined in header <algorithm>:

template< class T >                                               (1)
(since C++17)
constexpr const T& clamp( const T& v, const T& lo, const T& hi );

template< class T, class Compare >

constexpr const T& clamp( const T& v, const T& lo, const T& hi,   (2)
(since C++17)

                          Compare comp );

Here's the tea β˜• β€” if the value of v is within [lo, hi], it returns v; otherwise it returns the nearest boundary. That's the whole vibe check.

  1. Uses operator< (until C++20) or std::less{} (since C++20) to compare the values. If T is not LessThanComparable, the behavior is undefined.^[1]
  2. Uses the comparison function comp to compare the values.
⚠️ If lo is greater than hi, the behavior is undefined. Like, don't do that. Fr fr. πŸ’€
  1. ↑ If NaN is avoided, T can be a floating-point type.

🧚 Parameters

  • v β€” the value to clamp, bestie. The main character.
  • lo, hi β€” the boundaries to clamp v to. The supporting cast that keeps v in line.
  • comp β€” comparison function object (i.e. an object that satisfies the requirements of Compare) which returns true if the first argument is less than the second.

The signature of the comparison function should be equivalent to the following:

bool cmp(const Type1& a, const Type2& b);

While the signature does not need to have const&, the function must not modify the objects passed to it and must be able to accept all values of type (possibly const) Type1 and Type2 regardless of value category (thus, Type1& is not allowed, nor is Type1 unless for Type1 a move is equivalent to a copy (since C++11)). The types Type1 and Type2 must be such that an object of type T can be implicitly converted to both of them. πŸ™ˆ (yes that was a lot, but every word is load-bearing, iykyk)

πŸ’– Return value

Reference to lo if v is less than lo, reference to hi if hi is less than v, otherwise reference to v. Simple as that, no cap. ✨

πŸ”₯ Complexity

  1. At most two comparisons using operator< (until C++20) or std::less{} (since C++20).
  2. At most two applications of the comparison function comp.

Two. That's literally it. So efficient it's giving overachiever. 🫢

πŸŽ€ Possible implementation

                                     clamp (1)
template<class T>
constexpr const T& clamp(const T& v, const T& lo, const T& hi)
{
    return clamp(v, lo, hi, less{});
}

                                     clamp (2)
template<class T, class Compare>
constexpr const T& clamp(const T& v, const T& lo, const T& hi, Compare comp)
{
    return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
}

Look at that ternary. Chef's kiss. πŸ‘‘

πŸ’… Notes

Capturing the result of std::clamp by reference produces a dangling reference if one of the parameters is a temporary and that parameter is returned:

int n = -1;
const int& r = std::clamp(n, 0, 255); // r is dangling
😭 Do NOT do this. That r is dangling. It is not okay. It is going through things.

If v compares equivalent to either bound, returns a reference to v, not the bound. Noted and filed away, bestie. πŸ“

Feature-test macro time, and honestly the table is a vibe:

  • Feature-test macro: __cpp_lib_clamp
  • Value: 201603L
  • Std: (C++17)
  • Feature: std::clamp

🌈 Example

// Run this code

#include <algorithm>
#include <cstdint>
#include <iomanip>
#include <iostream>

int main()
{
    std::cout << "[raw] "
                 "[" << INT8_MIN << ',' << INT8_MAX << "] "
                 "[0," << UINT8_MAX << "]\n";

    for (const int v : {-129, -128, -1, 0, 42, 127, 128, 255, 256})
        std::cout << std::setw(4) << v
                  << std::setw(11) << std::clamp(v, INT8_MIN, INT8_MAX)
                  << std::setw(8) << std::clamp(v, 0, UINT8_MAX) << '\n';
}

⭐ Output

[raw] [-128,127] [0,255]
-129       -128       0
-128       -128       0
  -1         -1       0
   0          0       0
  42         42      42
 127        127     127
 128        127     128
 255        127     255
 256        127     255

See how -129 got yanked up to -128 and 256 got yanked down to 255? That's clamping, baby. Boundaries respected. Boundaries slayed. πŸ”₯

πŸ¦‹ See also

  • min β€” returns the smaller of the given values (function template)
  • max β€” returns the greater of the given values (function template)
  • in_range β€” checks if an integer value is in the range of a given integer type (C++20) (function template)
  • ranges::clamp β€” clamps a value between a pair of boundary values (C++20) (niebloid)

okay that's the whole page, bestie! 🫢 this icon was brought to you by http://cppreference.com, version 2024.06.10, filed under std::clamp(3) β€” go forth and clamp responsibly, mwah πŸ’‹πŸ’–

πŸ“¦ where this page comes from