Pages: [1]   Go Down
Author Topic: Passing a function another function  (Read 723 times)
0 Members and 1 Guest are viewing this topic.
Illinois, US
Offline Offline
Jr. Member
**
Karma: 2
Posts: 97
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello everybody!

So, I am trying to get my checkglass(); function to accept another function as an input condition, so it can use it throughout it's switch case parameters.  In my usage, the function I will be passing will be a drink recipe.  If the recipe is a shot, dataread counts how many shot glasses are present via switches attached by ShiftIn.  It then rotates the wheel between each shot, pouring the passed recipe each time before finally returning the wheel back to it's starting position.  If I can figure out how to pass a function another function, I can use the checkglass(); function like this:

Code:
checkglass(adiosMFer);

And that will make things very simple for me when I am typing up 200 some recipes.  The recipe functions have no input conditions.
Here is what I tried, and it didn't work:

Code:
void checkglass(void (*f))
{

 switch (dataread)
  {
  case 1:
   Serial.println("1 shotglass present");
   turnwheel(1);
   (*f);
   turnwheel(0);
    break;

  case 3:
   Serial.println("2 shotglasses present");
   turnwheel(1);
   (*f);
   turnwheel(2);
   (*f);
   turnwheel(0);
    break;
   
  case 7:
   Serial.println("3 shotglasses present");
   turnwheel(1);
   (*f);
   turnwheel(2);
   (*f);
   turnwheel(3);
   (*f);
   turnwheel(0);
    break;
   
  case 15:
   Serial.println("4 shotglasses present");
   turnwheel(1);
   (*f);
   turnwheel(2);
   (*f);
   turnwheel(3);
   (*f);
   turnwheel(4);
   (*f);
   turnwheel(0);
    break;
   
  case 31:
   Serial.println("5 shotglasses present");
   turnwheel(1);
   (*f);
   turnwheel(2);
   (*f);
   turnwheel(3);
   (*f);
   turnwheel(4);
   (*f);
   turnwheel(5);
   (*f);
   turnwheel(0);
    break;
   
  case 32:
   Serial.println("1 old fashioned glass present");
   turnwheel(6);
   (*f);
   turnwheel(0);
    break;
   
  case 64:
   Serial.println("1 highball glass present");
   turnwheel(7);
   (*f);
   turnwheel(0);
    break;

     }
}

any suggestions?  The error code comes up as:
Code:
barbotV18.ino: In function 'void loop()':
barbotV18:218: error: 'checkglass' was not declared in this scope
barbotV18.ino: In function 'void checkglass(void*)':
barbotV18:1288: error: 'void*' is not a pointer-to-object type

However it only tells me "'checkglass' was not declared in this scope" when I make it
Code:
void checkglass(void (*f))

if I leave it
Code:
void checkglass()

It doesn't give me any crap about not being declared in this scope.  Additionally, "'void*' is not a pointer-to-object type" is a mystery to me... I still absolutely blow at using pointers.  I'll get it eventually... I hope. 
Logged

Germany
Offline Offline
Edison Member
*
Karma: 133
Posts: 1444
If you believe something is right, you won't see what's wrong (David Straker).
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi

The best thing is to typedef your function declaration. This makes the code more readable.
Then: In C you just pass a function pointer, not the function itself.
But the good thing is, C treats function pointer just like functions, so a fn-ptr can be called like a function.

Code:
typedef void f_type(void);

void g( f_type *f) {
  f();
}

void fff(void) {
  puts("abc");
}

int main(void) {
  g(fff);
  return 0;
}
In the example, g calls fff, which outputs "abc".

Oliver
« Last Edit: February 10, 2013, 02:10:47 am by olikraus » Logged

Illinois, US
Offline Offline
Jr. Member
**
Karma: 2
Posts: 97
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

So I should try

Code:
typedef void f_type(void);

void glasscheck( f_type *f) {
  switch (dataread)
  {
  case 1:
   Serial.println("1 shotglass present");
   turnwheel(1);
   (f);
   turnwheel(0);
    break;

  case 3:
   Serial.println("2 shotglasses present");
   turnwheel(1);
   (f);
   turnwheel(2);
   (f);
   turnwheel(0);
    break;
   
  case 7:
   Serial.println("3 shotglasses present");
   turnwheel(1);
   (f);
   turnwheel(2);
   (f);
   turnwheel(3);
   (f);
   turnwheel(0);
    break;
   
  case 15:
   Serial.println("4 shotglasses present");
   turnwheel(1);
   (f);
   turnwheel(2);
   (f);
   turnwheel(3);
   (f);
   turnwheel(4);
   (f);
   turnwheel(0);
    break;
   
  case 31:
   Serial.println("5 shotglasses present");
   turnwheel(1);
   (f);
   turnwheel(2);
   (f);
   turnwheel(3);
   (f);
   turnwheel(4);
   (f);
   turnwheel(5);
   (f);
   turnwheel(0);
    break;
   
  case 32:
   Serial.println("1 old fashioned glass present");
   turnwheel(6);
   (f);
   turnwheel(0);
    break;
   
  case 64:
   Serial.println("1 highball glass present");
   turnwheel(7);
   (f);
   turnwheel(0);
    break;

     }
}


void VodkaOnRocks(void) {
    pour(1, vodkas, 8);
     End();
}


And then call it like

Code:
glasscheck(VodkaOnRocks);

?
Logged

Finland
Offline Offline
Jr. Member
**
Karma: 1
Posts: 84
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I think (f); should read f(); in the function, but otherwise that should work. It's either that or (*f)(); but those parentheses should appear at the end anyways, because it's a function call, I think.

Maybe irrelevant, but personally, I find function pointers a little awkward to work with. You are in effect doing code injection in C, when this kind of thing would be a little easier to handle in C++, in my opinion. You could have a base class that wraps the function as a virtual and then inherit the base class and redefine that function in each inherited class. Or you could have a single class with a pointer to another class that executes the function. Surely, the Arduino environment can compile all this, right?
Logged

New Jersey
Offline Offline
Faraday Member
**
Karma: 65
Posts: 3638
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You can certainly use a pointer to a function for this purpose, but I'm a little unsure as to why it's necessary here. I'd expect all you're really doing is specifying ingredients and amounts, so you just need to pass that data, which might well be just an index into an array of structs held in progmem. Even if there are special handling instructions like "stir" or "add ice", these can be specified in the data as flags.

Finally, if there really are drinks that do need a function to handle them, you might consider specifying them in the struct. Most of these function pointers would be null, but it would enable you to override the default handling for drinks that need something unusual. Disclaimer: I haven't done this on an Arduino so I'm not sure of the subtleties that the Harvard architecture brings here - it would likely take a bit of time figuring out with progmem directives.

I'm looking forward to seeing the barbot in action.
Logged

Offline Offline
Faraday Member
**
Karma: 60
Posts: 2816
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You can do it,   but you are asking for trouble.

Why does your drink recipe have to be a function ?
Logged

Temple, Texas
Offline Offline
Sr. Member
****
Karma: 14
Posts: 360
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@Jim_Socks

I seem to remember some quirkyness to Arduino typedefs, boiling down to requiring that certain functions must be "prototyped" .  Here is some working code that shows the technique you are looking for.  If you remove the "Arduino Quirk" prototype line, the compile will fail

Code:
typedef void (*drinkFun)(void);

void glasscheck( drinkFun fptr);  // Arduino quirk; must be prototyped

void glasscheck( drinkFun fptr){
    // ...
    fptr();
    // ...
}

void VodkaOnRocks(void) {
    Serial.println("Vodka On Rocks Fun");
}
void Margarita(void) {
    Serial.println("Margarita Fun");
}


void setup(){
   Serial.begin(19200);
   glasscheck( VodkaOnRocks);
   // or
   drinkFun rita=Margarita;
   glasscheck( rita);
}


void loop(){}

Cheers,
John

edit: renamed functions for domain
« Last Edit: February 10, 2013, 09:10:15 am by johncc » Logged

Illinois, US
Offline Offline
Jr. Member
**
Karma: 2
Posts: 97
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks guys!

I worked your code into mine Johncc, and it is telling me
Code:
barbotV18:40: error: variable or field 'checkglass' declared void
barbotV18:40: error: 'drinkfunction' was not declared in this scope

How should I declare drinkfunction- what type of pointer is it?


Edit:  spelling.  Drives me nuts lol
Logged

Temple, Texas
Offline Offline
Sr. Member
****
Karma: 14
Posts: 360
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I worked your code into mine Johncc, and it is telling me
Code:
barbotV18:40: error: variable or field 'checkglass' declared void
barbotV18:40: error: 'drinkfunction' was not declared in this scope
How should I declare drinkfunction- what type of pointer is it?

Hmm, my code should translate directly into yours, with names of your choosing.  If you don't want to post your whole code, probably need to post at least your:

typedef
function prototype
declaration of drinkFunction
declaration of checkglass
code that calls checkglass

John
Logged

United Kingdom
Offline Offline
Tesla Member
***
Karma: 224
Posts: 6593
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

You can certainly use a pointer to a function for this purpose, but I'm a little unsure as to why it's necessary here. I'd expect all you're really doing is specifying ingredients and amounts, so you just need to pass that data, which might well be just an index into an array of structs held in progmem. Even if there are special handling instructions like "stir" or "add ice", these can be specified in the data as flags.

Finally, if there really are drinks that do need a function to handle them, you might consider specifying them in the struct. Most of these function pointers would be null, but it would enable you to override the default handling for drinks that need something unusual. Disclaimer: I haven't done this on an Arduino so I'm not sure of the subtleties that the Harvard architecture brings here - it would likely take a bit of time figuring out with progmem directives.

I would use a class to represent drinks. That way, you can represent most recipes just by storing different data; but if you really do need special steps for some drinks, you can override the function that does the dispensing and mixing as well - which is neater than using function pointers.
Logged

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

New Jersey
Offline Offline
Faraday Member
**
Karma: 65
Posts: 3638
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
I would use a class to represent drinks. That way, you can represent most recipes just by storing different data; but if you really do need special steps for some drinks, you can override the function that does the dispensing and mixing as well - which is neater than using function pointers.

I did consider that, but I'm not sure how you would initialize the necessary array of classes with the drink data at compile time.
Logged

United Kingdom
Offline Offline
Tesla Member
***
Karma: 224
Posts: 6593
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Classes and structs are the same thing, except that the members are private in classes and public in structs until you specify otherwise. So you can initialize classes in exactly the same way as structs. Having said that:

1. It is more usual to initialise classes by calling their constructors; and

2. If you are going to make use of function overloading, then you actually need an array of pointers (or references) to objects. Here is one way of achieving that:

Code:
const Drink d1("Vodka Martini", 1, 3, ...);
const ShakenDrink d2("Vodka Martini shaken not stirred", 1, 3, ...);
const Drink d3("Screwdriver, 0, 4, ...);
const
...

const Drink *drinks[] = {&d1, &d2, &d3, ...};

I've assumed you have declared suitable constructors, and that ShakenDrink derives from drink.
Logged

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Pages: [1]   Go Up
Jump to: