According to [alg.clamp#5], the time complexity of std::ranges::clamp requires at most 2 comparisons and 3 application of projections. The possible implementation in cppreference is given by:
struct clamp_fn {
template<class T, class Proj = std::identity,
std::indirect_strict_weak_order<std::projected<const T*, Proj>> Comp = ranges::less>
constexpr const T& operator()(const T& v, const T& lo, const T& hi,
Comp comp = {}, Proj proj = {}) const
{
assert(!std::invoke(comp, std::invoke(proj, hi), std::invoke(proj, lo)));
return std::invoke(comp, std::invoke(proj, v), std::invoke(proj, lo)) ? lo
: std::invoke(comp, std::invoke(proj, hi), std::invoke(proj, v)) ? hi : v;
}
};
inline constexpr clamp_fn clamp;
which obviously does not meet the requirements, since it involves 3 comparisons and 6 projections. Even if we comment out the assert, the number of projections is still 4 because std::invoke(proj, v) was executed twice.
The only way I can think of is to temporarily store the result of std::invoke(proj, v), then pass it to the next two comp calls, just like the libstdc++ does:
auto&& __proj_val = std::__invoke(__proj, __val);
if (std::__invoke(__comp, __proj_val, std::__invoke(__proj, __lo)))
return __lo;
else if (std::__invoke(__comp, std::__invoke(__proj, __hi), __proj_val))
return __hi;
else
return __val;
But for safety, we seem to be unable to use std::forward<decltype(__proj_val)>(__proj_val) to perfect forward the __proj_val in the first comp call, which means that we seem to be unable to use only 3 projections to perfectly implemented the std::ranges::clamp.
Why does std::ranges::clamp limits the number of projections so strictly? Does this mean that it is necessary to temporarily store the result of projection for the complexity requirement? Or is my understanding of this complexity requirement wrong?