If you are willing to write your own clamp function, you can detect this and do something about it.
For example, suppose you want the clamp function to return by value if that is the only way to ensure that we don't get a dangling reference:
#include <type_traits>
template<class A, class B, class C>
constexpr std::conditional_t<
std::is_lvalue_reference<A &&>::value && std::is_lvalue_reference<B &&>::value && std::is_lvalue_reference<C &&>::value,
std::add_lvalue_reference_t<std::common_type_t<A, B, C>>,
std::common_type_t<A, B, C>
> clamp(A && value, B && min, C && max)
{
return value < min ? min : max < value ? max : value;
}
This would make clamp(n, 0, 255)
effectively have the signature int clamp(int&, int&&, int&&)
; you'd only get int & clamp(int&, int&, int&)
if all 3 inputs are lvalue references. Making the returned reference const
is trivial if you so desire.
You could also have the function fail to compile if they weren't all lvalue references:
#include <type_traits>
template<class A, class B, class C>
constexpr std::add_lvalue_reference_t<std::common_type_t<A, B, C>>
clamp(A && value, B && min, C && max)
{
static_assert(std::is_lvalue_reference<A &&>::value && std::is_lvalue_reference<B &&>::value && std::is_lvalue_reference<C &&>::value, "");
return value < min ? min : max < value ? max : value;
}
std
, you might add an deleted overload liketemplate <typename T, typename U, typename V> constexpr std::enable_if_t<std::is_rvalue_reference<T&&>::value || std::is_rvalue_reference<U&&>::value || std::is_rvalue_reference<V&&>::value> clamp(T&& value, U&& min, V&& max) = delete;
– Tragedianint r = std::clamp(n, 0, 255);
– Hawkingstd::clamp(n, 0, 255);
. – Tragedian