Compare function template

After a recent thread dealing with comparing values against a range, at least a once-a-week issue here, I decided to try my hand at writing a function to simplify the process. I decided on a template form so that different types of numeric values could be compared – like against like.

The end game is to put this in library form to cut the effort to an #include but Win7pro isn’t cooperating.

In the meantime I’d appreciate any comments or observations on this effort.

/*
   ARDUINO LIMIT INSTRUCTION

  A C++ template with two modes to compare a
  numeric argument against two like values.

  mode a. If low limit value is less than or
  equal to high limit value, logic true is returned
  if argument is equal to or between the limits.
  False is returned otherwise.

  given: a = 3, b = 6, c = 9:
  example ( a <= b <= c ) returns true, returns false if b = 11

  mode b. Low limit is greater than high limit - if argument is
  between both limits a logic false is returned - true is
  returned otherwise.

  given: a = 8, b = 6, c = 4
  example - ( b < a and b > c ) returns false,
*/

template <class T> bool limitTest (T lowLimit, T argument, T hiLimit) {

  if (lowLimit <= hiLimit) {
    if (lowLimit <= argument and argument <= hiLimit)
      return true;
    else return false;
  }
  else if ( argument < lowLimit and argument > hiLimit)
    return false;
  else return true;
}

void setup() {
  Serial.begin(230400);

  int lowlim = 3;
  int hilim = 5;
  Serial.println("LOW LIM LESS THAN OR EQUAL HILIM\n");
  Serial.print("LOW  ARG  HI\n");
  for (int i = 1 ; i < 7; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);
    
    if (limitTest(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();

   lowlim = 5;
   hilim = 3;
  Serial.println("LOW LIM GREATER THAN HILIM\n");
  Serial.print("LOW  ARG  HI\n");
  for (int i = 1; i < 7; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);
    
    if (limitTest(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();

  lowlim = 5;
  hilim = 5;
  Serial.println("LOW LIM  EQUAL HILIM\n");
  Serial.print("LOW  ARG  HI\n");
  for (int i = 3; i < 8; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);
    
    if (limitTest(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();
}
void loop() {
  // put your main code here, to run repeatedly:

}

I do not understand having a template that takes a class, and not expecting the class to do the comparison between instances.

Your template would allow me to pass two instance of the Dog class to the function, and would tell me if Fido was less than Spot. How the hell is it going to do that, without calling a static method in the Dog class, to ask it whether Fido is less than, equal to, or greater than Spot?

dougp:
The end game is to put this in library form to cut the effort to an #include but Win7pro isn’t cooperating.

In the meantime I’d appreciate any comments or observations on this effort.

Remember that function templates are function templates. That is, the compiler will write the function in each of the versions that is used. If you are passing three different types in your program, three functions will be written. For that reason, you define them in a header file, not in the implementation file, which is what I suspect is giving you the problem with Win7pro.

PaulS means something like this:

template <typename T> 
bool limitTest (T lowLimit, T argument, T hiLimit) {

dougp:
After a recent thread dealing with comparing values against a range, at least a once-a-week issue here,

I wonder are the people asking the questions sufficiently experienced to use your solution?

...R

BulldogLowell:
Remember that function templates are function templates. That is, the compiler will write the function in each of the versions that is used. If you are passing three different types in your program, three functions will be written.

Thanks for the reply. I did glean that from the various examples and tutorials I checked as a starting point. To establish this, it's my understanding that one function will be created to deal with ints, another to deal with floats, and yet another to deal with longs. So far so good?

BulldogLowell:
For that reason, you define them in a header file, not in the implementation file, which is what I suspect is giving you the problem with Win7pro.

Can I presume 'implementation file' refers to the .cpp file part of the library?

Here are the header and cpp files I created with Word. In the confusion I've lost track of how I got them into local Disk(C:)\Program Files (x86)\Arduino\libraries\limit but, now I'm not given access to this directory. I've changed the permissions to allow write access but as soon as I exit the dialog box and try to do that it's set back to read only. Probably doesn't matter at this point since I assume the files are incorrect anyway.

// limit function  header
#ifndef limitTest_h
#define limitTest_h
#include “Arduino.h”
class compare
{
public:
  limitTest (T lowLimit, T argument, T hiLimit);
   
};
#endif
#include "Arduino.h"
#include "limit.h"
limit::limit ()
/*
   ARDUINO LIMIT INSTRUCTION

A C++ template which compares a numeric argument against two values. If low limit
  value is less than or equal to high limit value, a logic
  true is returned if argument is greater than or equal to or
  between the limits - false is returned otherwise.

  If low limit is greater than high limit and argument is
  between both limits a logic false is returned - true is
  returned otherwise.
*/

template <class T> bool limitTest (T lowLimit, T argument, T hiLimit) {

  if (lowLimit <= hiLimit) {
    if (lowLimit <= argument and argument <= hiLimit)
      return true;
    else return false;
  }
  else if ( argument < lowLimit and argument > hiLimit)
    return false;
  else return true;
}

Robin2:
I wonder are the people asking the questions sufficiently experienced to use your solution?
...R

Who can say? It does seem to me though, that those unfamiliar with programming languages in general and the unforgiving syntax rules (a lot of them with fewer than ten posts) stumble over these:

    • properly setting up the low limit and high limit compares
    • properly placing the .and.
    • interpreting the result

I feel that a simple form like a <= b <= c returning a Boolean result might help people visualize what's going on and set up the comparison right the first time.

You shouldn't put your own libraries in the Arduino installation directory :wink:

3rd party libraries should go in the libraries directory where the sketches are.

I feel that a simple form like a <= b <= c returning a Boolean result might help people visualize what's going on and set up the comparison right the first time.

Although I applaud your attempt, it's my feeling that that way people will never learn.

dougp:
Thanks for the reply. I did glean that from the various examples and tutorials I checked as a starting point. To establish this, it's my understanding that one function will be created to deal with ints, another to deal with floats, and yet another to deal with longs. So far so good?

well, it will create a function for each type that it is passed during compilation... which is why your idea falls apart when making a comparison like this:

if (compare(1, 3.4, 9)) {

because the compiler cannot deduce that you mean to pass all three arguments as floats.

you could try fixing that with something like (untested):

template <typename A,typename B,typename C> bool limitTest (A lowLimit, B argument, C hiLimit) {

  if (static_cast<B>(lowLimit) <= static_cast<B>(hiLimit)) {
    if (static_cast<B>(lowLimit) <= argument and argument <= static_cast<B>(hiLimit))
      return true;
    else return false;
  }
  else if ( argument < static_cast<B>(lowLimit) and argument > static_cast<B>(hiLimit))
    return false;
  else return true;
}

casting the limits to the argument, kind of thing

dougp:
Probably doesn't matter at this point since I assume the files are incorrect anyway.

why the addition of a (compare) class?

what the heck is this:

limit::limit ()

if you can allow me to editorialize, why would you need this function? At this point, you've learned how you need to make multiple comparisons. If for some strange reason you actually need it, why the crappy name "compare"?

sterretje:
You shouldn't put your own libraries in the Arduino installation directory :wink:

3rd party libraries should go in the libraries directory where the sketches are.

Aah. Thanks, I'll start over with that.

sterretje:
Although I applaud your attempt, it's my feeling that that way people will never learn.

There are libraries to put everything imaginable 'under the hood', why not something like this?

It's my observation that the majority of folks who post questions here aren't particularly interested in learning anything more than how to get their project working with a minimum of fuss - the how seems a minor concern.

Anyhow, if this little idea were accepted it might at least save a bit of forum time explaining comparisons and logic.

author=BulldogLowell date=1519127115 link=msg=3615338]
well, it will create a function for each type that it is passed during compilation... which is why your idea falls apart when making a comparison like this:

if (compare(1, 3.4, 9)) {

because the compiler cannot deduce that you mean to pass all three arguments as floats.

I discovered that. I tested what I have by using mixed types in the call and the compiler complained accordingly. It was not my intent to have a function which would accept any type for any of the three arguments but I guess that would make it more useful.

BulldogLowell:
you could try fixing that with something like (untested):

template <typename A,typename B,typename C> bool limitTest (A lowLimit, B argument, C hiLimit) {

if (static_cast(lowLimit) <= static_cast(hiLimit)) {
   if (static_cast(lowLimit) <= argument and argument <= static_cast(hiLimit))
     return true;
   else return false;
 }
 else if ( argument < static_cast(lowLimit) and argument > static_cast(hiLimit))
   return false;
 else return true;
}



casting the limits to the argument, kind of thing

Thanks! Added to the project to-do list.

BulldogLowell:
why the addition of a (compare) class?

Following the example in the make your own library tutorial, it looked like a class name was needed so that's what I came up with.

author=BulldogLowell link=msg=3615338 date=1519127115]
what the heck is this:

limit::limit ()

Again, just trying to follow the tutorial example.

BulldogLowell:
if you can allow me to editorialize, why would you need this function? At this point, you've learned how you need to make multiple comparisons. If for some strange reason you actually need it, why the crappy name "compare"?

I don't need it. As stated in the opening post, it would, it is hoped, benefit those for whom setting up these comparisons is unknown territory - as making a library is unknown territory for me. I'm thinking of folks who use only one '=' for comparisons and other simple absolute beginner mistakes.

As for the name, it sounded like a good idea at the time, somewhat descriptive if nothing else. When I get this working I'll go back and tidy up names and commentary and such.

dougp:
Following the example in the make your own library tutorial, it looked like a class name was needed so that's what I came up with.

you don't have a class... you have a function... or possibly a few functions.

If you fall in love with a utility that you develop... start to build your own library... But, templates stay in the header file!

ino file:

#include "MyHelpers.h"

void setup() {
  Serial.begin(9600);
  if (limitTest(1,3.3,7)) {
    Serial << (F("True\n"));
  } else {
    Serial << (F("False\n"));
  }

}

void loop() {

}

header file:

#ifndef MYHELPERS_H
#define MYHELPERS_H

// specialty template functions

template <class T> inline Print& operator << (Print& obj, T arg)
{
  obj.print(arg);
  return obj;
}

template <typename A,typename B,typename C> bool limitTest (A lowLimit, B argument, C hiLimit) {

  if (static_cast<B>(lowLimit) <= static_cast<B>(hiLimit)) {
    if (static_cast<B>(lowLimit) <= argument and argument <= static_cast<B>(hiLimit))
      return true;
    else return false;
  }
  else if ( argument < static_cast<B>(lowLimit) and argument > static_cast<B>(hiLimit))
    return false;
  else return true;
}

#endif

I tried this and got these messages:

Arduino: 1.8.2 (Windows 7), Board: "Arduino/Genuino Uno"

In file included from C:\Users\Owner\Documents\Arduino\limit_instruction_bulldog_header\limit_instruction_bulldog_header.ino:1:0:

MyHelpers.h:7: error: 'T' has not been declared

template inline Print& operator << (Print& obj, T arg)

^

MyHelpers.h:7: error: 'operator<<' is not a template function

template inline Print& operator << (Print& obj, T arg)

^

MyHelpers.h:8: error: expected ';' before '{' token

{

^

MyHelpers.h:13: error: explicit instantiation of non-template 'bool limitTest'

template bool limitTest (A lowLimit, B argument, C hiLimit) {

^

MyHelpers.h:13: error: expected ';' before '(' token

template bool limitTest (A lowLimit, B argument, C hiLimit) {

^

exit status 1
'T' has not been declared

when the following two files were used: (UNO R3 BTW)

Main file

#include "MyHelpers.h"

void setup() {
Serial.begin(9600);
if (limitTest(1,3.3,7)) {
Serial << (F("True\n"));
} else {
Serial << (F("False\n"));
}

}

void loop() {

}

Header

#ifndef MYHELPERS_H
#define MYHELPERS_H

// specialty template functions

template inline Print& operator << (Print& obj, T arg)
{
  obj.print(arg);
  return obj;
}

template bool limitTest (A lowLimit, B argument, C hiLimit) {

  if (static_cast(lowLimit) <= static_cast(hiLimit)) {
    if (static_cast(lowLimit) <= argument and argument <= static_cast(hiLimit))
                                 return true;
        else return false;
  }
      else if ( argument < static_cast(lowLimit) and argument > static_cast(hiLimit))
        return false;
  else return true;
}

#endif

I attempted to fix the semicolon error in line eight by placing one at various locations. This only generates different errors. The rest are impenetrable.

In other news, I did get *this * file to compile and produce correct results:

Main file

#include "MyLimit.h"

void setup() {
  Serial.begin(230400);

  int lowlim = 3;
  int hilim = 5;
  Serial.println("LOW LIM LESS THAN OR EQUAL HILIM\n");
  Serial.print("LOW  ARG  HI\n");
  for (int i = 1 ; i < 7; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);

    if (limitTest(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();

  lowlim = 5;
  hilim = 3;
  Serial.println("LOW LIM GREATER THAN HILIM\n");
  Serial.print("LOW  ARG  HI\n");
  for (int i = 1; i < 7; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);

    if (limitTest(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();

  lowlim = 5;
  hilim = 5;
  Serial.println("LOW LIM  EQUAL HILIM\n");
  Serial.print("LOW  ARG  HI\n");
  for (int i = 3; i < 8; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);

    if (limitTest(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();
}
void loop() {
  // put your main code here, to run repeatedly:

}

Header

template <class T> bool limitTest (T lowLimit, T argument, T hiLimit) {

  if (lowLimit <= hiLimit) {
    if (lowLimit <= argument and argument <= hiLimit)
      return true;
    else return false;
  }
  else if ( argument < lowLimit and argument > hiLimit)
    return false;
  else return true;
}

Yeaa!

I got a working version

Demo sketch

#include "numericRangeFunc.h" // located in sketch folder
//#include <numericRangeFunc.h> // located in Library folder

void setup() {
  Serial.begin(230400);

  int lowlimA = 3;
  unsigned long hilimA = 4;
  Serial.println("**** Numeric range function demo ****\n");
  Serial.println("LOW LIM LESS THAN OR EQUAL HILIM\n");
  Serial.println("LOW = int, ARG = float, HI = UL\n");
  
  for (float i = 1.80 ; i < 5.17;) {
    Serial.print(" ");
    Serial.print(lowlimA);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilimA);

    if (isInRange(lowlimA, i, hilimA)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
    i = i + .43;
  }
  Serial.println();

  float  lowlim = 6.5;
  byte hilim = 3;
  Serial.println("LOW LIM GREATER THAN HILIM\n");
  Serial.println("LOW = float, ARG = uint8_t, HI = byte\n");
  for (uint8_t i = 2; i < 9; i++) {
    Serial.print(" ");
    Serial.print(lowlim);
    Serial.print(" <= ");
    Serial.print(i);
    Serial.print(" <= ");
    Serial.print(hilim);

    if (isInRange(lowlim, i, hilim)) Serial.println(" returns \ttrue");
    else Serial.println(" returns false");
  }
  Serial.println();
}
void loop() {
  // put your main code here, to run repeatedly:

}

Header file

// numericRangeFunc.h

/*
   ARDUINO NUMERIC RANGE FUNCTION

  A C++ template with two modes to compare a numeric argument
  against a low and high limit value.  Each of the three
  values can be of mixed types consisting of : int, float,
  uint8_t, unsigned long, or byte.

  mode a. If low limit value is less than or equal to high
  limit value: logic true is returned if argument is equal
  to either limit or between the limits.  False is returned
  otherwise.

  given: a = 3, b = 6, c = 9:
  example ( a <= b <= c ) returns true, returns false if b = 11

  mode b. Low limit is greater than high limit - if argument
  is between both limits a logic false is returned.  If argument
  is equal to or outside either limit logic true is returned.

  given: a = 8, b = 6, c = 4
  example - ( b < a and b > c ) returns false,
*/

#ifndef NUMERICRANGEFUNC_H
#define NUMERICRANGEFUNC_H

template < class T, class U, class V > bool isInRange
( T lowLimit, U argument, V hiLimit ) {

  if ( lowLimit <= hiLimit ) {
    if ( lowLimit <= argument and argument <= hiLimit )
      return true;
    else return false;
  }
  else if ( argument < lowLimit and argument > hiLimit )
    return false;
  else return true;
}

#endif

dougp:
I got a working version

you're OK with using implicit casting?

So, I can now use your code to determine if an instance of the Giraffe class is between an instance of the Elephant class and an instance of the Ferrari class, and you can tell that with no help from any of the classes involved. Hmmm. Call me skeptical, but I REALLY don't believe that.

PaulS:
So, I can now use your code to determine if an instance of the Giraffe class is between an instance of the Elephant class and an instance of the Ferrari class, and you can tell that with no help from any of the classes involved. Hmmm. Call me skeptical, but I REALLY don't believe that.

or even an unsigned elephant to an elephant!

BulldogLowell:
or even an unsigned elephant to an elephant!

Would an elephant allow you to get close enough to sign it?

BulldogLowell:
you're OK with using implicit casting?

I take that to mean implicit casting is something to be avoided. Would you care to suggest an alternative?

dougp:
I take that to mean implicit casting is something to be avoided. Would you care to suggest an alternative?

You would want to know that if you are comparing signed vs unsigned vs floating point numbers that you would get the answer you expected. for example, if you are testing:

bool result = compare(27, 5 * PI, 100.3);

you'd want to be aware that both limits were 1) being cast to a float during your comparison and 2) were subject to the limitations of floats (i.e. 100.3 actually equals 100.3000030517578125)

the example I provided cast the limits to the tested variable as a possible solution. your example relies on implicit casting on both comparisons.

PaulS:
I do not understand having a template that takes a class, and not expecting the class to do the comparison between instances.

There is literally no difference at all between class and typename when used in a template parameter list. I use class exclusively simply because it's shorter. Less clutter is always good.

Your template would allow me to pass two instance of the Dog class to the function, and would tell me if Fido was less than Spot. How the hell is it going to do that, without calling a static method in the Dog class, to ask it whether Fido is less than, equal to, or greater than Spot?

This is not a less-than function, it is a window-detect. Notice that there are both a high and low limit.

The classes would need to have the relational operators defined for them. For a Dog class that might not make any sense, but for something like a wrapping_index it definitely makes sense because things like order is easily understood for numbers.

PaulS:
So, I can now use your code to determine if an instance of the Giraffe class is between an instance of the Elephant class and an instance of the Ferrari class, and you can tell that with no help from any of the classes involved. Hmmm. Call me skeptical, but I REALLY don't believe that.

Save yourself some embarrassment and learn a thing or two about templates before you start babbling like an idiot.

The Giraffe, Elephant, and Ferrari are not the same class, and presumably not even convertible or comparable to each other. There's no way they'd work, and if you tried to do something that stupid the compilation will fail.

If, however, you could define <= and >= functions that will work between those classes, than yes the template function will work perfectly fine.

To the topic at hand now, templates are less useful for straight numerical calculations like this because the exact data type of the number is not important. map() isn't a template for example, it just uses all longs as arguments. It would probably be best to create one function for integers and one for floats. You can even use your existing template function as the main workhorse, and just specify exactly what type you want to use when you call it:

template < class T > bool isInRange_helper
( T lowLimit, T argument, T hiLimit ) {

  if ( lowLimit <= hiLimit ) {
    if ( lowLimit <= argument and argument <= hiLimit )
      return true;
    else return false;
  }
  else if ( argument < lowLimit and argument > hiLimit )
    return false;
  else return true;
}

bool isInRange( long lowLimit, long argument, long hiLimit )
{
    return isInRange_helper<long>(lowLimit, argument, hiLimit);
}

bool isInRangeF( float lowLimit, float argument, float hiLimit )
{
    return isInRange_helper<float>(lowLimit, argument, hiLimit);
}

Couple of minor quibbles:

There's no need to use any of those if statements, especially not an "if (expression) return true; else return false". Just return the expression itself (or its negation as appropriate).

if ( lowLimit <= hiLimit ) {
    return lowLimit <= argument and argument <= hiLimit;
  }
  else{
    return !(argument < lowLimit and argument > hiLimit );
  }

The name of your function is also an issue. isInRange implies that it should return true if it is between the limits and false if it is not. Your function has the option of inverting that behavior, which screws with the semantics of the name and could cause confusion.