c++ passing functions with variable number of arguments as argument to other function

126 Views Asked by At

I wrote a C++ class that parses expressions like "2 * SQRT(5) + 1". I've created a class called c_function that "represents" the usual mathematical functions like sqrt, sin, cos etc. something like as follows:

class c_function {
  std::string name;
  double (*function)(double);

public:
  c_function(std::string fname, double (*ffunction)(double)) {
    name = fname;
    function = ffunction;
  }

// ...
};

Then I have a different class that contains a std::vector of these c_function objects:

class expression {
  std::vector<c_function> functions;
  // etc...

public:
  // ctor:
  expression(/* ... */) {
    // ...
    functions.push_back(c_function("SQRT", sqrt));
    functions.push_back(c_function("SIN" , sin));
    functions.push_back(c_function("COS" , cos));
    // ...
  }

};

The point is that all these functions have one argument. That is fine for most cases but I want to enable adding custom functions to the expression class and also want to support custom functions with more that one argument (for example, to define a function AREA(a, b) that returns the product of the two values a and b).

I did that by adding an argument counter argumentCount and more function "properties" to the c_function class:

class c_function {
  std::string name;
  unsigned int argumentCount;
  double (*function1)(double);
  double (*function2)(double, double);

  // ...
};

and used two constructors:

c_function(std::string fname, double (*ffunction)(double)) {
  name = fname;
  argumentCount = 1;
  function1 = ffunction;
  function2 = NULL;
};
c_function(std::string fname, double (*ffunction)(double, double)) {
  name = fname;
  argumentCount = 2;
  function1 = NULL;
  function2 = ffunction;
};

and added the methods to the expression class:

// add custom function with one argument
void addFunction(std::string fname, double (*ffunction)(double));

// add custom function with two arguments
void addFunction(std::string fname, double (*ffunction)(double, double));

so that one can define

double Expression_Area(double width, double height) {
  return (width * height);
}

and introduce it to the expression class with

myExpression.addFunction("AREA", Expression_Area);

That works fine and this way I can also add more function "properties" and functions constructors allowing any number of arguments, but

  1. there is always a limit of the number of arguments that is supported
  2. the code becomes ugly by having multiple constructors, methods to add a function, and code within the interpretation of the expression just because the number of arguments may be different.

I wonder if there is a way to support functions with any number of arguments more general. I tried changing the c_function class to:

class c_function {
  std::string name;
  unsigned int argumentCount;
  double (*function)(...);
  // ...

};

but this does not work because functions with fixed number of arguments are not accepted by (...).

Is there any way to get this solved with one constructor, one function "property" etc.?

1

There are 1 best solutions below

5
On BEST ANSWER

C++11 onwards

In C++11 you can use a variadic template to declare a class that will take a function with a variable number of arguments:

#include <iostream>
#include <string>

double bar(double x) {
  return x;
}

double bar2(double x, double y) {
  return x+y;
}

template <typename... Args>
class Foo {
public:
  Foo (const std::string& name, double (*func)(Args...))
    : name_{name}, func_{func} {}

  double call(Args... args) {
    return func_(args...);
  }

  // Thanks to W.F. for reminding me of the operator() overload
  // This does the same thing as a call to the "call" method.
  double operator()(Args... args) {
    return func_(args...);
  }

private:
  std::string name_;
  double (*func_)(Args...);
};

int main() {
  Foo<double> t1("test1", bar);
  Foo<double, double> t2("test2", bar2);

  // NOTE: in C++17 you can declare Foo without the template
  // arguments: they will be deduced.
  // Foo t1("test1", bar); // C++17
  // Foo t2("test2", bar2); // C++17

  std::cout << t1.call(14) << ' ' << t2(14, 56) << std::endl;

  return 0;
}

You can tweak this basic solution as to how you need your classes to work.

C++98

Pre C++11, you will probably be forced to create classes that take functions with a different number of arguments, so your classes would look something like this:

#include <iostream>
#include <string>

double bar(double x) {
  return x;
}

double bar2(double x, double y) {
  return x+y;
}

class Foo1 {
public:
  // let's typedef the function pointer for two reasons:
  //   1. readability
  //   2. duplicating the class for a different number of arguments
  //      means we need to do less modifications to the code because
  //      we can catch a few changes in one line here.
  typedef double (*Function)(double);

  Foo1 (const std::string& name, const Function func)
    : name_(name), func_(func) {}

  double operator()(double x) {
    return func_(x);
  }

private:
  std::string name_;
  Function func_;
};

class Foo2 {
public:
  typedef double (*Function)(double, double);

  Foo2 (const std::string& name, const Function func)
    : name_(name), func_(func) {}

  double operator()(double x, double y) {
    return func_(x, y);
  }

private:
  std::string name_;
  Function func_;
};

// etc. for classes Foo3, Foo4, ... up until you think you will
// need no more.

int main() {
  Foo1 t1("test1", bar);
  Foo2 t2("test2", bar2);

  std::cout << t1(14) << ' ' << t2(14, 56) << std::endl;

  return 0;
}

There's a bit of code duplication here, but it's not too bad.

Remarks

Finally, (although I did not show it above because I think it goes without saying) wherever there is code duplication, template the types so that you can reduce this as much as possible. So, for example, you might consider modifying the C++11 Foo class above to:

template <typename T, typename... Args>
class Foo {
public:
  typedef T (*Function)(Args...);

  Foo (const std::string& name, const Function func)
    : name_{name}, func_{func} {}

  T operator()(Args... args) {
    return func_(args);
  }

private:
  std::string name_;
  Function    func_;
};