Reputation: 117
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
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.?
Upvotes: 1
Views: 146
Reputation: 6816
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.
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.
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_;
};
Upvotes: 3