Notifications
Clear all

Some MACRO tidbits

15 Posts
3 Users
3 Likes
338 Views
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

I was working an issue earlier today and saw the need for a macro or two. I also have not seen many or maybe any macros being used on the forum so maybe some of the newer members are unfamiliar with them, I am sure all the old boys like me are very familiar. I also have a feeling my C++ friends will show me how they can do it better. The first macro is a simple "is this number between two other numbers" while the second is just a test/debug print statement I didn't feel like copy/paste/editing so I made a macro instead. Let me know if you have questions, corrections, improvements (especially the comparison, I think it can be better)

  

struct Range{
  int Start;
  int End;
} ;

Range Q1 = {  1,  90};
Range Q2 = { 91, 180};
Range Q3 = {181, 270};
Range Q4 = {271, 360};

#define IN_RANGE(NUM, RANGE) (((NUM >= RANGE.Start) && (NUM <= RANGE.End)))

#define PRINT_RTEST(Var, Range) Serial.println(String(Var) + " is between " + String(Range.Start) + " " + String(Range.End) + " inclusive.")

void setup() {
  int Number;
  
  Serial.begin(9600);

  Number = 179;

  if (IN_RANGE(Number, Q1)) PRINT_RTEST(Number, Q1);
  else Serial.println(String(Number) + " is not in Q1");

  if (IN_RANGE(Number, Q2)) PRINT_RTEST(Number, Q2); 
  else Serial.println(String(Number) + " is not in Q2");

  if (IN_RANGE(Number, Q3)) PRINT_RTEST(Number, Q3); 
  else Serial.println(String(Number) + " is not in Q3");

  if (IN_RANGE(Number, Q4)) PRINT_RTEST(Number, Q4); 
  else Serial.println(String(Number) + " is not in Q4");

  Serial.println(" ===== ");
  Number = 180;

  if (IN_RANGE(Number, Q1)) PRINT_RTEST(Number, Q1);
  else Serial.println(String(Number) + " is not in Q1");

  if (IN_RANGE(Number, Q2)) PRINT_RTEST(Number, Q2); 
  else Serial.println(String(Number) + " is not in Q2");

  if (IN_RANGE(Number, Q3)) PRINT_RTEST(Number, Q3); 
  else Serial.println(String(Number) + " is not in Q3");

  if (IN_RANGE(Number, Q4)) PRINT_RTEST(Number, Q4); 
  else Serial.println(String(Number) + " is not in Q4");

  Serial.println(" ===== ");
  Number = 181;

  if (IN_RANGE(Number, Q1)) PRINT_RTEST(Number, Q1);
  else Serial.println(String(Number) + " is not in Q1");

  if (IN_RANGE(Number, Q2)) PRINT_RTEST(Number, Q2); 
  else Serial.println(String(Number) + " is not in Q2");

  if (IN_RANGE(Number, Q3)) PRINT_RTEST(Number, Q3); 
  else Serial.println(String(Number) + " is not in Q3");

  if (IN_RANGE(Number, Q4)) PRINT_RTEST(Number, Q4); 
  else Serial.println(String(Number) + " is not in Q4");

  Serial.println(" ===== ");
}

void loop() {

}

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
Quote
Will
 Will
(@will)
Famed Member
Joined: 2 years ago
Posts: 2218
 

@zander

More of a niggle than a criticism, but I think you'd be better off using something like

#define BETWEEN(NUM,LOW,HIGH)    ((NUM>=LOW) && (NUM<=HIGH))

Reasons ...

1) all three values specified

2) no need to create an pointless struct

3) independent of struct component names

4) equivalent form is BETWEEN(37,Q1.Start,Q1.End)

Experience is what you get when you don't get what you want.


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

@will Excellent point Master Will. I was addressing a specific use case, and should have mentioned the BETWEEN macro was not generic. More like a set operator really. I will steal thay (with your permission of course) and back fit it into my use case, that way it's a win win. Thanks buddy.

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote
frogandtoad
(@frogandtoad)
Noble Member
Joined: 4 years ago
Posts: 1506
 

@zander

Posted by: @zander

I was working an issue earlier today and saw the need for a macro or two. I also have not seen many or maybe any macros being used on the forum so maybe some of the newer members are unfamiliar with them, I am sure all the old boys like me are very familiar. I also have a feeling my C++ friends will show me how they can do it better. The first macro is a simple "is this number between two other numbers" while the second is just a test/debug print statement I didn't feel like copy/paste/editing so I made a macro instead. Let me know if you have questions, corrections, improvements (especially the comparison, I think it can be better)

[snipped example code]

Your macros look ok to me, but... their use is frowned upon these days, and there are better or cleaner ways to achieve the same result.

A few good reasons not to use them may include:
  Ugly and hard to read as they get bigger
  They do not have a namespace, and as such they do not have any scope, and can break code easily
  Very difficult to debug as they are a simple text replacement by the preprocessor

I don't see anything wrong with using a struct or class as you have, but if you're just going to use it the way you've shown, then there is a better way to implement your desired functionality using one

I'll cobble up as an alternative for you shortly...

Cheers


   
Ron reacted
ReplyQuote
frogandtoad
(@frogandtoad)
Noble Member
Joined: 4 years ago
Posts: 1506
 

@zander

Posted by: @frogandtoad

@zander

Posted by: @zander

I was working an issue earlier today and saw the need for a macro or two. I also have not seen many or maybe any macros being used on the forum so maybe some of the newer members are unfamiliar with them, I am sure all the old boys like me are very familiar. I also have a feeling my C++ friends will show me how they can do it better. The first macro is a simple "is this number between two other numbers" while the second is just a test/debug print statement I didn't feel like copy/paste/editing so I made a macro instead. Let me know if you have questions, corrections, improvements (especially the comparison, I think it can be better)

[snipped example code]

Your macros look ok to me, but... their use is frowned upon these days, and there are better or cleaner ways to achieve the same result.

A few good reasons not to use them may include:
  Ugly and hard to read as they get bigger
  They do not have a namespace, and as such they do not have any scope, and can break code easily
  Very difficult to debug as they are a simple text replacement by the preprocessor

I don't see anything wrong with using a struct or class as you have, but if you're just going to use it the way you've shown, then there is a better way to implement your desired functionality using one

I'll cobble up as an alternative for you shortly...

Cheers

OK, here is an example for you to ponder on and play around with.
Please feel free to ask any questions:

struct isBetween {
  isBetween(int nMin, int nMax) : nMin(nMin), nMax(nMax) {}
  
  int nMin;
  int nMax;
  
  bool number(int num, bool log = false) {
    
    if(num >= nMin && num <= nMax) {
      if(log) {
        Serial.println("The number (" + String(num) + ") is in range");
       }
         
        return true;
       }
       
      if(log) {
        Serial.println("The number (" + String(num) + ") is out of range");
       }
     
      return false; 
     }
 };

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

  isBetween(1, 101).number(1);
  isBetween(1, 101).number(101);
  isBetween(1, 101).number(0);
  isBetween(1, 101).number(102);
  
  if(isBetween(1, 101).number(42, true)) {
    Serial.println("\tStarting up the flux capacitor...\n");
   } 
 }

void loop() {
  // ...
 }

As I said previously, this is something I just cobbled up as an example - I could think up some better names, but hopefully it conveys the point.

image

Cheers 


   
Ron reacted
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

@frogandtoad I actually agree with you re all the reasons macros are not a good idea, and I see that Arduino recommends against them also. Since I was still seeing a vast majority of folks and examples using #define vs const I assumed anarchy still ruled the IT world. It sounds like there may be hope.

My path to the macros was very random and I was trying to work out a way to simplify a specific coding problem but then tried to turn it into a generic solution. My BAD.

As to your solution, as you may remember I do not understand C++ so the syntax looks very weird to me. I am going to make a guess that the statement below is doing dual duty, one as a constructor and one as a member thingy??? Don't get me wrong, I like terse language in many cases especially now that my old brain has such a small cache size. The second question is that 0 is NOT between 1 and 101 so where is the message or error or something, is there yet another C++ feature I am not knowing about?

isBetween(1, 101).number(0);

 

 

 

 

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote
frogandtoad
(@frogandtoad)
Noble Member
Joined: 4 years ago
Posts: 1506
 
Posted by: @zander

@frogandtoad I actually agree with you re all the reasons macros are not a good idea, and I see that Arduino recommends against them also. Since I was still seeing a vast majority of folks and examples using #define vs const I assumed anarchy still ruled the IT world. It sounds like there may be hope.

My path to the macros was very random and I was trying to work out a way to simplify a specific coding problem but then tried to turn it into a generic solution. My BAD.

As to your solution, as you may remember I do not understand C++ so the syntax looks very weird to me. I am going to make a guess that the statement below is doing dual duty, one as a constructor and one as a member thingy??? Don't get me wrong, I like terse language in many cases especially now that my old brain has such a small cache size. The second question is that 0 is NOT between 1 and 101 so where is the message or error or something, is there yet another C++ feature I am not knowing about?

isBetween(1, 101).number(0);

I'll try to explain the best I can.

Firstly, "isBetween" as a struct (and is also considered a class in C++), and "number(num)" is a member function of that class.

I created a custom constructor "isBetween(min, max)" to accept the two values for flexibility of use.

Calling "isBetween(1, 101)" creates a "temporary" object, so rather than creating a persistent object:

    isBetween rangeObject(1, 101);

... and then calling the member function upon it:

    rangeObject.number(0);

... it's easier to just use the temporary object and call the member function directly upon it:

    isBetween(min, max).number(num_to_check);

Does that make sense?

In essence, I'm using a class as a better function.

As for your second question, the number function has a default parameter, thats why I demonstrated both versions... if you add "true" for the third parameter, you should see the message 😉

Cheers


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

@frogandtoad I could use a lesson. I see nMin and nMax used 3 or 4 times and I suspect they are not really the same thing. Could you re-do that code snippet with each variable in a different namespace using a different name so I get a better sense of what is happening.

Also why does the entire thing start with struct, I see no pieces as in my example.

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  
Posted by: @frogandtoad
Posted by: @zander

@frogandtoad I actually agree with you re all the reasons macros are not a good idea, and I see that Arduino recommends against them also. Since I was still seeing a vast majority of folks and examples using #define vs const I assumed anarchy still ruled the IT world. It sounds like there may be hope.

My path to the macros was very random and I was trying to work out a way to simplify a specific coding problem but then tried to turn it into a generic solution. My BAD.

As to your solution, as you may remember I do not understand C++ so the syntax looks very weird to me. I am going to make a guess that the statement below is doing dual duty, one as a constructor and one as a member thingy??? Don't get me wrong, I like terse language in many cases especially now that my old brain has such a small cache size. The second question is that 0 is NOT between 1 and 101 so where is the message or error or something, is there yet another C++ feature I am not knowing about?

isBetween(1, 101).number(0);

I'll try to explain the best I can.

Firstly, "isBetween" as a struct (and is also considered a class in C++), and "number(num)" is a member function of that class.

I created a custom constructor "isBetween(min, max)" to accept the two values for flexibility of use.

Calling "isBetween(1, 101)" creates a "temporary" object, so rather than creating a persistent object:

    isBetween rangeObject(1, 101);

... and then calling the member function upon it:

    rangeObject.number(0);

... it's easier to just use the temporary object and call the member function directly upon it:

    isBetween(min, max).number(num_to_check);

Does that make sense?

In essence, I'm using a class as a better function.

As for your second question, the number function has a default parameter, thats why I demonstrated both versions... if you add "true" for the third parameter, you should see the message 😉

Cheers

GOT IT, thanks. However not enough to start writing it, just need to understand enough to read it. 

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote
frogandtoad
(@frogandtoad)
Noble Member
Joined: 4 years ago
Posts: 1506
 

@zander

Posted by: @zander

@frogandtoad I could use a lesson. I see nMin and nMax used 3 or 4 times and I suspect they are not really the same thing. Could you re-do that code snippet with each variable in a different namespace using a different name so I get a better sense of what is happening.

Also why does the entire thing start with struct, I see no pieces as in my example.

Although they have the same name, indeed they are in different scope and the constructor knows the difference - The important thing to note is where they go in the constructor initialisation list.  Here is another example to clear up that portion of the code:

struct isBetween {
  
  isBetween(int a, int b) : nMin(a), nMax(b) {}
  
  int nMin;
  int nMax;
  
  bool number(int num, bool log = false) {
    
    if(num >= nMin && num <= nMax) {
      if(log) {
        Serial.println("The number (" + String(num) + ") is in range");
       }
         
        return true;
       }
       
      if(log) {
        Serial.println("The number (" + String(num) + ") is out of range");
       }
     
      return false; 
     }
 };

When we specify nMin and nMax directly after the colon (which signifies the start of the constructor initialisation list), they refer directly to their declared variable names, thus:

constructorName(param1, param2) : var1(param1), var2(param2) {/*empty body*/}

I am using a struct, I'm not sure what you mean by pieces?


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

@frogandtoad Thanks, that helps. I will study that. In case I have not mentioned it, muy brain was injured at birth so now I often don't remember proper names of things like struct pieces? as in name, address etc. Maybe struct fields?

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote
frogandtoad
(@frogandtoad)
Noble Member
Joined: 4 years ago
Posts: 1506
 

@zander

Posted by: @zander

@frogandtoad Thanks, that helps. I will study that. In case I have not mentioned it, muy brain was injured at birth so now I often don't remember proper names of things like struct pieces? as in name, address etc. Maybe struct fields?

No worries.

In C++ terminology, class variables are referred to as "data members" or "member data" when generalising. Likewise, functions are called "member functions".

Cheers


   
Ron reacted
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

@frogandtoad Ok, got it, for now LOL.

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote
frogandtoad
(@frogandtoad)
Noble Member
Joined: 4 years ago
Posts: 1506
 

@zander 

Forgot to mention yesterday, with respect to how macros don't respect namespaces and have no scope.  This was actually a classic example of why to avoid macros if you can!

Initially, I wanted to name my class variables 'min ' and 'max' for my example, but look what happened:

image
image

Because Arduino.h is implicitly included in every sketch, it also implicitly includes the macros 'min' and 'max' via other header files. Even if I wrap my own code with a namespace, the macros will still not respect them... so I was forced to use 'nMin' and 'nMax' to my disliking - This is how code is easily broken by using macros, as they are now always in scope when you don't want them to be, polluting the global namespace.

Cheers


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3915
Topic starter  

@frogandtoad EXCELLENT example. 

My profuse apologies for my (hopefully) brief wanderings in the land of frankenstein code. At least I hope my transgressions were educational. Now if everyone would stop using them (including #define) then life would be easier for those of us who spend a lot of time trying to fix others code.

Arduino says and I agree, in general, the const keyword is preferred for defining constants and should be used instead of #define
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon
My experience hours are >75,000 and I stopped counting in 2004.
Major Languages - 360 Macro Assembler, Intel Assembler, PLI/1, Pascal, C plus numerous job control and scripting


   
ReplyQuote