5

I am using an an API that takes a function with a single argument as a callback. The callback takes a single argument of a certain type, and for simplicity, I'll say it returns a bool. The main thing I was trying to put together was a range checking function. My intuition would be to write something like this:

template<class T, T min, T max>
constexpr bool in_range(T val) {
    return (val >= min && val <= max);
}

static_assert(in_range<float, 0.0f, 1.0f>(0.5f), "doesn't work")

However, that doesn't work, so I defaulted to creating a function this way.

template<class T>
std::function<bool(T)> in_range(T min, T max) {
    auto test = [min, max](T val) {
        return (val >= min && val <= max);
    };
    return test;
}


assert(in_range<float>(0.0f, 1.0f)(0.5f))

Is there a way to write the function more in the form of the first function, so I'm not depending on std::function and lambdas generated at runtime?

dfreese
  • 378
  • 1
  • 10
  • 2
    Why not make `min` and `max` function parameters? – NathanOliver May 09 '18 at 18:16
  • 1
    I should have clarified that I'm running in a c++11 environment, so the auto return value isn't an option. – dfreese May 09 '18 at 18:24
  • 1
    And I haven't made them into function parameters, as the API only takes a callback with a single parameter. I could just write the lambda when setting the callback, but it's a pattern that I would like to make concise and clear as it is reused multiple times. – dfreese May 09 '18 at 18:37
  • Is the issue here specifically that the float type is excluded from function templates? Could you write an alternate spec for floats where the 2 value arguments were doubles? – Gem Taylor May 09 '18 at 19:10
  • @GemTaylor `double`s aren't allowed as template non-type arguments either. – Justin May 09 '18 at 19:11
  • 2
    Are you sure you're running a C++11 environment? In your example you write a `static_assert()` without error message that, if I remember correctly, is a C++17 feature. – max66 May 09 '18 at 19:13
  • Fair enough. I had a vague memory that specifically floats were excluded from pemplate types. I have in the past resolved this by inventing a fixed-point enum representation, but it really isn't pretty. Of course in the particular example, they could just as easily be ints! – Gem Taylor May 09 '18 at 19:14
  • @max66, correct. I added the static assert for the example and forgot that. The actual code doesn't actually use that. – dfreese May 09 '18 at 19:16
  • What about user-defined literals? :-) – Gem Taylor May 09 '18 at 19:18

2 Answers2

4

As floats aren't allowed as template non-type parameters, you'd have to take them as actual function arguments rather than template parameters.

If you want to have a function which only takes one argument, you can avoid the cost of the std::function by returning the lambda directly. If we were using C++14, you could just make it return auto:

template<class T>
auto in_range(T min, T max) { // Note: could be `constexpr` in C++17
    return [min, max](T val) {
        return (val >= min && val <= max);
    };
}

However, since you are using C++11, you'd have to write out the callable type manually:

template <typename T>
class InRange {
public:
    constexpr InRange(T min, T max)
        : min(std::move(min))
        , max(std::move(max))
    {}

    constexpr bool operator()(T const& val) const {
        return (val >= min && val <= max);
    }

private:
    T min;
    T max;
};

template<class T>
constexpr InRange<T> in_range(T min, T max) {
    return InRange<T>(std::move(min), std::move(max));
}
Justin
  • 24,288
  • 12
  • 92
  • 142
2

Is there a way to write the function more in the form of the first function, so I'm not depending on std::function and lambdas generated at runtime?

If you can pass static global variables as template argument instead of float literals, you can pass they by reference

Something as follows

#include <iostream>

template <typename T, T const & min, T const & max>
constexpr bool in_range (T val)
 { return (val >= min && val <= max); }

static constexpr float  f0 { 0.0f };
static constexpr float  f1 { 1.0f };

int main ()
 {    
   static_assert(in_range<float, f0, f1>(0.5f), "!");
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    @Barry - Reference template `float`'s are accepted as template parameter where simple `float` parameters don't. So this answer the cited question. – max66 May 09 '18 at 19:05
  • What makes static global variables allowable, and literals not? That's not something I'm familiar with. – dfreese May 09 '18 at 19:25
  • Ahh, didn't realize it was a thing with float. https://stackoverflow.com/questions/2183087/why-cant-i-use-float-value-as-a-template-parameter – dfreese May 09 '18 at 19:30
  • @dfreese: It's that they are references more like. References are really pointers in disguise, which satisfy the integral constraint of template arguments. – AndyG May 09 '18 at 19:31
  • @dfreese - exactly: your original example didn't works because floating points values aren't permitted templates argument (14.1.7: "A non-type template-parameter shall not be declared to have floating point, class, or void type"). But, in same point of standard, there is an example that show that a pointer or a reference to a `double` is OK as not-type template argument. – max66 May 09 '18 at 19:34