jbelenus
jbelenus

Reputation: 503

Clamp using with real_t from Godot-cpp

I'm trying to use clamp, but can't find it

A summary example

File: test.h

#ifndef DEMO_H
#define DEMO_H

#include <algorithm>

namespace demo {

   typedef float real_t;
   class test {
       public:
           void start();
           real_t x = 10.90;
           real_t y = 2.10;
       test();
       ~test();
   };
}

#endif //DEMO_H

File: test.cpp

#include "test.h"

using namespace demo;

void test::start() {
    const int& p = std::clamp(x, 0,  y);
}

VSC error, compile using scons:

no overloaded function instance "std :: clamp" matches the argument list - argument types are: (real_t, int, real_t)

Thanks a lot!

Upvotes: 0

Views: 808

Answers (1)

walnut
walnut

Reputation: 22152

The problem is that std::clamp is declared as follows:

template<class T>
constexpr const T& clamp(const T&, const T&, const T&);

That means that all three arguments must have the same type. If the template argument is not explicitly specified, then deduction will fail, because successful template argument deduction requires that all function parameters from which a template argument is deduced yield the same type.

But in your example, you use:

std::clamp(x, 0,  y);

which uses function arguments of type float, int, float in that order. Therefore the first and third function argument will deduce T to float, but the second one will deduce it to int, causing deduction, and thereby overload resolution, to fail.

Make sure that you use the same types:

std::clamp(x, real_t(0), y);

In the line

const int& p = std::clamp(x, 0,  y);

you are using the wrong type for p. I assume that you meant it to be real_t, not int. You can avoid such type mismatches by using auto:

const auto& p = std::clamp(x, 0,  y);

Also, it is not save to store the result of std::clamp to a reference if not all arguments to std::clamp are lvalues.

std::clamp returns one of its arguments by-reference. So it may happen that the second argument real_t(0), which is a temporary, is returned by-reference. But that temporary is destroyed after the line

    const auto& p = std::clamp(x, real_t(0),  y);

so p might be a dangling reference. Instead store the result by-value:

    auto p = std::clamp(x, real_t(0),  y);

This technically doesn't apply to your line with variable type const int&, because the type mismatch between that and the returned reference from std::clamp will cause creation of a temporary which will then bind to p and be life-time extended. But this is not something one should rely on.


In a comment you link you should an alternative implementation for clamp as a macro.

A macro here as a significant drawback: It will evaluate its arguments multiple times. If you want a std::clamp alternative that accepts different types, then you can write it as a function template yourself, e.g.:

template<typename T, typename U, typename V>
constexpr auto clamp(const T& t, const U& u, const V& v) {
    return (t < u) ? u : (v < t) ? v : t;
}

Note that in this case it is important that the function returns by-value, because the ?: operator might result in a prvalue depending on the argument types.

Also think carefully about whether you actually want to use clamp with different argument types (This applies to both the implementation I show above as well as the macro you are using). You could easily accidentally make unintended comparisons, for example if one of the arguments is a signed integer and the other an unsigned one, the comparison with < will yield unintended results. This is probably why the standard library does not allow different argument types.

Upvotes: 2

Related Questions