Creating an array of callbacks

Hi I am trying to create an array of callbacks and lambdas. in most cases i've sort of managed

typedef void (*gsmFunction)();
gsmFunction callBack[20];


void someFunc(){
 Serial.println("done something");
}

callBack[0] = someFunc;

but in many cases the callbacks will need to take an argument. for example

void gsmSendSMS(char *number, char *message){}

I had hoped that I would be able to use lambas for this. e.g

callBack[i] = [&](char *number){ gsm.write(number); gsm.write("\r\n"); gsmWaitFor(">", "+CME ERROR:");};
i++;
callBack[i] = [&](char *message){gsm.write(message); gsm.write(char(26)); gsmWaitFor("OK", "+CME ERROR:");};

but the callBack array is expecting only functions of type gsmFunction which has no function arguments.

probably I could wrap every function to be used as a callback in a class but I wonder whether there is a procedural solution where the callBack array could simply hold arbitrary function pointers. Such as a std::function type? but that does not seem to be available for the nano or mega2560 boards (or any?)

is there any route to success here?

thanks Justin

Will a simple cast not work?

that does not seem to be available for the nano or mega2560 boards (or any?)

I very much doubt that the board type has anything to do with it. Can you do what you want in vanilla C++ ? If not then forget it, because the Arduino IDE uses C++.

Just make all callbacks accept as many parameters as you need. They can just ignore it if they do not need them. It doesn't really make sense because you are trying to create an array of apples, but store oranges.

If you know when an element is an apple, or orange (parameters or not) then you simply do not need the array, it isn't serving a purpose from what it sounds like.

std::function isn't included, but it is expensive and uses dynamic memory.

I thought a lambda with captures would not cast to a function pointer, however this seems to work.

int myFunc( int* a, int* b ){
  return 4;
}

typedef int (*fptr)(int*, int*);

fptr array[] = {
  [](int* a, int* b){ return 1; },
  [&](int* a, int* b){ return 2; },
  [=](int* a, int* b){ return 3; },
  myFunc,
};

void setup() {
  int a,b;

  for( auto el : array )
    el( &a, &b );
}

void loop() {}

Here is what I usually use; the functions simply have a pointer to a void as argument.

void someFunc(void *ptrIn)
{
  Serial.println((char*)ptrIn);
}

void otherFunc(void *ptrIn)
{
  Serial.println("Welcome back my friends, to the show that never ends");
}

void (*callBacks[])(void *arguments) =
{
  someFunc,
  otherFunc
};


void setup()
{
  callBacks[0]((void*)"Hello world");
  callBacks[1](NULL);
}

void loop()
{
}

You can basically pass any type of argument; to pass multiple parameters like a number and a C string, place them in a struct and pass the pointer to the struct. If a callback does not require an argument, simply pass NULL.

Since each 'callback' is apparently called from a different context then 'array' might not be the right data structure. Have you considered using a 'struct'?

struct callbacks {
  void (*pNumberString)(const char *number);
  void (*pMessageString)(const char *message);
} Callbacks;

void setup() {
  Callbacks.pNumberString = CBNumberString;
  Callbacks.pMessageString = CBMessageString;
}

void loop() {
  Callbacks.pNumberString("90210");
  Callbacks.pMessageString("Hello world.");
}


void CBNumberString(const char *number) {
  Serial.write(number);
  Serial.write("\r\n");
}

void CBMessageString(const char *message) {
  Serial.write(message);
  Serial.write(char(26));
}
1 Like

hmm.

well first off thanks for all the rapid replies.

what they have really done is expose my own ignorance to myself! I have been coding for just under 40 years but almost exclusively in interpreted languages where I have rarely had to concern myself with type casting.

@UKHeliBob
board type may be relevant as, for example, there are boards that do have available.
can I do this in vanilla C++; yes. I believe so. by having the callback array stack holding std::functions or vectors. But I am not sure at all as I am no expert in C++.

@AWOL
do you mean:

callBack[i] = (gsmFunction) [&](char &message) {};

if so, then no. It does not seem to work.

@pYro_65
I am not sure I follow you. How is it possible to make a procedural function accept arbitrary arguments of arbitrary types?
the array is necessary as it is a circular stack of callbacks. at any time the stack might have 15 or more commands that need to be executed sequentially. the overall intent of the code is to create an asynchronous framework for the instruction and execution of gsm AT commands.
or rather I think that the array is necessary but there may be alternative solutions. An FSM might be one if I could be sure that there were no changeable variables within each command set (which is not the case).
in your sample code each function type was essentially of the same nature - a pointer to a function that returned an int and took two int arguments. I am needing to create an array where the function pointed to in that array are always returning void but might take no arguments, a char array or some other arbitrary thing.

I suppose a deeply ugly solution would be to create arrays of each function type that is used
and then create a holding array which would contain a struct pointing to the array type and the key of that array. then the callback code would choose what to execute based on that struct.

e.g. (and NB not tested for syntax or structural errors)

typedef void (*gsmFunction1)();
typedef void (*gsmFunction2)(char *array);


typedef struct gsmCommandTypes{
  int type;
  int key;
};

gsmFunction1 callBack1[20];
gsmFunction2 callBack2[20];

gsmCommandTypes callBack[40];


void addCallBack(gsmFunction1 func1, gsmFunction2 func2){
  if(func1 == NULL){
    callBack2[callBack2Pos] = func2;
    callBack[callBackPos] = {2,callBack2Pos};
    callBackPos++;
    callBack2Pos++;
  } else {
    callBack1[callBack1Pos] = func1;
    callBack[callBackPos] = {1,callBack1Pos};
    callBackPos++;
    callBack1Pos++;
  }
}

doCallBack(){
  for(int i=0; i<40; i++){
    if(callBack[i] == NULL) continue;
    if(callBack[i].type == 1){
      callBack1[callBack[i].key]();
    } else {
      callBack2[callBack[i].key]();
    }
    break;
 }
}

deeply ugly as the number of callBack types increases with every arbitrary function type so I am hopeful there is a cleaner/simpler solution

@johnwasser - thanks that looks very like my idea above. but it feels like an ugly hack to something that should be easier to do given that lambdas are supported these days. i think that a struct to hold the actual callback stack would not work and a sequential array is needed. but the array could hold a struct. whether the struct held pointers to the lamda function itself or pointers to an array of lamda functions would be a matter for which was better for memory consumption. I'd have to test.

@sterretje - thanks likewise. that looks like a possible solution too. i will have to see how that can be adapted for lambdas with closures.

I am needing to create an array where the function pointed to in that array are always returning void but might take no arguments, a char array or some other arbitrary thing.

Then the functions can accept a set of void*/char* like sterretje was pointing out, and you can cast in the function to whatever type it should be receiving. Parameters that aren't needed can be ignored.

the array is necessary as it is a circular stack of callbacks. at any time the stack might have 15 or more commands that need to be executed sequentially.

how will you know whether the function accepts 0 or 3 parameters? In this code you can send the dummy variables for functions that do not need all or some of the parameters.

how will you know whether the function accepts 0 or 3 parameters

because if it is not a zero argument function I will assign it to an array as a lambda function with its own closure.

Then the functions can accept a set of void*/char* like sterretje was pointing out, and you can cast in the function to whatever type it should be receiving.

that has been an eye-opener!

If you really want an array you can use a union to handle the argument and return types. This will, of course, cause bizarre behavior if the wrong union member is chosen for the given index in the array.

union funcs {
  void (*pNumberString)(const char *number);
  float (*pMessageString)(const char *message);
};

union funcs Callbacks[20];

void setup() {
  Callbacks[0].pNumberString = CBNumberString;
  Callbacks[1].pMessageString = CBMessageString;
}

void loop() {
  Callbacks[0].pNumberString("90210");
  static float foo = Callbacks[1].pMessageString("Hello world.");
  foo++;  // Avoid 'unused variable' warning
}

void CBNumberString(const char *number) {
  Serial.write(number);
  Serial.write("\r\n");
}

float CBMessageString(const char *message) {
  Serial.write(message);
  Serial.write(char(26));
  return 5.5;
}