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.