What would you have them use instead of constant references? If you take values and T is bigint, then clamp becomes unusable. Even if it’s 128 bit ints... that’s worse.
I think const ref makes perfect sense. It has the logic you want but no copying etc. When clamp is inlined, the compiler should be able to deal with const refs as easily as values for small Ts. (And if it’s not, let’s fix the compilers... not stdlib)
Having everything be const references is in a sense optimal, but is so prone to misuse that I wouldn't want it in the standard library.
One option is to pass everything by value. Move semantics does the rest. Like:
template<typename T>
T clamp(T v, T lo, T hi) {
if(v < lo) v = std::move(lo);
if(v > hi) v = std::move(hi);
return std::move(v);
}
For fundamental types, this is the same or better than before (including __int128_t and such). For bigint types with heap allocation, i.e., GMP wrappers and such, moving is fairly inexpensive (but for constant lo, hi, they need to be created/copied each time...). For fixed-width bigint types, moving is no different from copying and the cost is higher. In any case, the 99-percentile usage of these functions is with fundamental types, where pass-by-value is clearly the better choice.
Alternatively, you could have
template<typename T>
T& clamp(T& v, T const& lo, T const& hi) {
if(v < lo) v = lo;
if(v > hi) v = hi;
return v;
}
This forces the returned reference to be an l-value, and so returning dangling temporaries becomes more difficult. There's the added cost for the assignments, though.
- clamp(a, b, c), where a, b, c are all l-values would copy everything. Not destructive by default (but wasteful on "big" types).
- clamp(a, std::move(b), c) would potentially destroy b, but that's something the caller explicitly opted into.
- clamp(a, T{ something }, T{ something else }) would be an implicit move, but nobody is going to accidentally use those r-values anywhere else.
The non-const ref version returning is not something I quite like either; it would make more sense to me to have `void clamp(T& x, T const& l, T const& h)`, where the semantics would be more like `x.clamp(l, h);`. But to retain the same API I returned the reference.
I think const ref makes perfect sense. It has the logic you want but no copying etc. When clamp is inlined, the compiler should be able to deal with const refs as easily as values for small Ts. (And if it’s not, let’s fix the compilers... not stdlib)