7

It is better not to bind std::clamp return value to const ref, if one of its min or max parameters are rvalues.

Typical realization of std::clamp (very simplified):

template <class T>
constexpr const T& clamp(const T& value, const T& min, const T& max)
{
    return value < min ? min : max < value ? max : value;
}

And as already stated in cppreference for std::clamp there is a dangerous situation when someone write:

int n = -1;
const int& r = std::clamp(n, 0, 255);
// r is dangling

Is there any way for compile-time-detect these cases?

vladon
  • 8,158
  • 2
  • 47
  • 91
  • 1
    Is there any way to catch this with either **AddressSanitizer** or **UndefinedBehaviorSanitizer**? I've tried both and unfortunately can't seem to get them to complain. – Vittorio Romeo Jan 26 '17 at 09:31
  • 4
    If it was not in `std`, you might add an deleted overload like `template constexpr std::enable_if_t::value || std::is_rvalue_reference::value || std::is_rvalue_reference::value> clamp(T&& value, U&& min, V&& max) = delete;` – Jarod42 Jan 26 '17 at 09:33
  • @Jarod42 Thanks, but such a way you will delete a possibility to use `int r = std::clamp(n, 0, 255);` – vladon Jan 26 '17 at 09:51
  • 5
    This looks like a defect in the standard library. – Richard Hodges Jan 26 '17 at 09:53
  • 2
    @vladon: You cannot check function argument according of the usage of the returned value. in both cases, it is `std::clamp(n, 0, 255);`. – Jarod42 Jan 26 '17 at 10:07
  • Why was this template function designed to return a reference in the first place? – A.S.H Jan 26 '17 at 11:50
  • @A.S.H To avoid copy of big objects passed to it. – vladon Jan 26 '17 at 11:54
  • @vladon I guess so, but most if not all of its typical use-cases is with numbers. Not omparing and clamping big objects.... – A.S.H Jan 26 '17 at 11:56
  • @vladon But they could still have overloaded for r-values, puzzles me.. – Viktor Sehr Jan 07 '18 at 22:35

1 Answers1

2

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;
}
Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    Makes me wonder why the hell (sorry for the language) didn't implement std::clamp this way. At least they could have prevented rvalues – Viktor Sehr Jan 07 '18 at 22:35