How to create a container that holds different types of function pointers in C++?
Asked Answered
P

4

1

I'm doing a linear genetic programming project, where programs are bred and evolved by means of natural evolution mechanisms. Their "DNA" is basically a container (I've used arrays and vectors successfully) which contain function pointers to a set of functions available. Now, for simple problems, such as mathematical problems, I could use one type-defined function pointer which could point to functions that all return a double and all take as parameters two doubles.

Unfortunately this is not very practical. I need to be able to have a container which can have different sorts of function pointers, say a function pointer to a function which takes no arguments, or a function which takes one argument, or a function which returns something, etc (you get the idea)...

Is there any way to do this using any kind of container ? Could I do that using a container which contains polymorphic classes, which in their turn have various kinds of function pointers? I hope someone can direct me towards a solution because redesigning everything I've done so far is going to be painful.

Panay answered 15/1, 2011 at 10:9 Comment(3)
Say that you have a container c with lots of different kinds of functions. How are you going to call them? How are you going to process function results for the non-void functions?Essive
I'd call them by iterating through the container and sequentially execute the function that is pointed to by the function pointer. I am not so sure if what i want to do is possible. For non void functions I would just assign the return value to a void pointer and try to handle it (is that even possible?).Monoplane
Why store the function pointers? Why not just store an identifier that you can use to choose the function to execute? (E.g., switch(FuncId){ case MULT: ... break; } ) Either way, you'll need some method of meaningfully applying the function chosen (e.g., where does the return value of a function go?)Kampmeier
N
2

A typical idea for virtual machines is to have a separate stack that is used for argument and return value passing.

Your functions can still all be of type void fn(void), but you do argument passing and returning manually.

You can do something like this:

class ArgumentStack {
    public:
        void push(double ret_val) { m_stack.push_back(ret_val); }

        double pop() {
             double arg = m_stack.back();
             m_stack.pop_back();
             return arg;
        }

    private:
        std::vector<double> m_stack;
};
ArgumentStack stack;

...so a function could look like this:

// Multiplies two doubles on top of the stack.
void multiply() {
    // Read arguments.
    double a1 = stack.pop();
    double a2 = stack.pop();

    // Multiply!
    double result = a1 * a2;

    // Return the result by putting it on the stack.
    stack.push(result);
}

This can be used in this way:

// Calculate 4 * 2.
stack.push(4);
stack.push(2);
multiply();
printf("2 * 4 = %f\n", stack.pop());

Do you follow?

Norven answered 15/1, 2011 at 10:30 Comment(5)
No not really and it's a shame cuz it seems like u have something here... Have u got a link I can find out more about custom stacks ?Monoplane
Maybe you can try learning a little Forth. en.wikipedia.org/wiki/Forth_%28programming_language%29 It uses the same approach, which also inspired Java VM (if I am right) Please ask again if you need more help.Norven
@Alex you may want to have a look at en.wikipedia.org/wiki/Stack-oriented_programming_languageLudie
This seems like it could work with some adjustments, but this would only change the arguments passed into a function pointer. Would this have to be done to the functions that the function pointer points too as well ? About FORTH, this is really interesting but sadly I do not have the time to adapt all this into another language or learn it at this moment, however thank you for letting me know!Monoplane
@Alex: Yes, the functions would need to be rewritten, however you only need to change the parts related to argument passing and return values - rather trivial.Norven
V
1

You cannot put a polymorphic function in a class, since functions that take (or return) different things cannot be used in the same way (with the same interface), which is something required by polymorphism.

The idea of having a class providing a virtual function for any possible function type you need would work, but (without knowing anything about your problem!) its usage feels weird to me: what functions would a derived class override? Aren't your functions uncorrelated?

If your functions are uncorrelated (if there's no reason why you should group them as members of the same class, or if they would be static function since they don't need member variables) you should opt for something else... If you pick your functions at random you could just have several different containers, one for function type, and just pick a container at random, and then a function within it.

Could you make some examples of what your functions do?

Verisimilitude answered 15/1, 2011 at 10:14 Comment(1)
Yes some functions are uncorrelated, but not all, for example the function set for solving first degree polynomials is basically: add, subtract, divide, multiply, root, square and also swap (two variables) and reverse (make a number from positive, negative or vice versa ). However in more complex problems, such as 2nd degree polynomials, or differentials, I need to use functions which compare values, functions which have a "logic" or a simple algorithm, etc... These functions might need a reference or to return a value and are thus completely different from the first set.Monoplane
F
1

What you mentioned itself can be implemented probably by a container of std::function or discriminated union like Boost::variant.
For example:

#include <functional>
#include <cstdio>
#include <iostream>

struct F {
  virtual ~F() {}
};

template< class Return, class Param = void >
struct Func : F {
  std::function< Return( Param ) >  f;
  Func( std::function< Return( Param ) > const& f ) : f( f ) {}
  Return operator()( Param const& x ) const { return f( x ); }
};

template< class Return >
struct Func< Return, void > : F {
  std::function< Return() >  f;
  Func( std::function< Return() > const& f ) : f( f ) {}
  Return operator()() const { return f(); }
};

static void f_void_void( void ) { puts("void"); }
static int f_int_int( int x ) { return x; }

int main()
{
  F  *f[] = {
    new Func< void >( f_void_void ),
    new Func< int, int >( f_int_int ),
  };

  for ( F **a = f, **e = f + 2;  a != e;  ++ a ) {
    if      ( auto p = dynamic_cast< Func< void >*     >( *a ) ) {
      (*p)();
    }
    else if ( auto p = dynamic_cast< Func< int, int >* >( *a ) ) {
      std::cout<< (*p)( 1 ) <<'\n';
    }
  }
}

But I'm not sure this is really what you want...
What do you think about Alf P. Steinbach's comment?

Farlee answered 15/1, 2011 at 12:49 Comment(1)
Im not so sure I understand your code, but it seems like what you have coded could work. I hadn't thought too much about what Steinbach comment, and still aren't really sure about it.Monoplane
U
0

This sort of thing is possible with a bit of work. First it's important to understand why something simpler is not possible: in C/C++, the exact mechanism by which arguments are passed to functions and how return values are obtained from the function depends on the types (and sizes) of the arguments. This is defined in the application binary interface (ABI) which is a set of conventions that allow C++ code compiled by different compilers to interoperate. The language also specifies a bunch of implicit type conversions that occur at the call site. So the short and simple answer is that in C/C++ the compiler cannot emit machine code for a call to a function whose signature is not known at compile time.

Now, you can of course implement something like Javascript or Python in C++, where all values (relevant to these functions) are typed dynamically. You can have a base "Value" class that can be an integer, float, string, tuples, lists, maps, etc. You could use std::variant, but in my opinion this is actually syntactically cumbersome and you're better of doing it yourself:

enum class Type {integer, real, str, tuple, map};

struct Value
{
  // Returns the type of this value.
  virtual Type type() const = 0;
  
  // Put any generic interfaces you want to have across all Value types here.
};

struct Integer: Value
{
  int value;

  Type type() const override { return Type::integer; }
};

struct String: Value
{
  std::string value;

  Type type() const override { return Type::str; }  
};

struct Tuple: Value
{
  std::vector<Value*> value;

  Type type() const override { return Type::tuple; };
}

// etc. for whatever types are interesting to you.

Now you can define a function as anything that takes a single Value* and returns a single Value*. Multiple input or output arguments can be passed in as a Tuple, or a Map:

using Function = Value* (*)(Value*);

All your function implementations will need to get the type and do something appropriate with the argument:

Value* increment(Value* x)
{
  switch (x->type())
  {
    Type::integer:
      return new Integer(((Integer*) x)->value + 1);
    Type::real:
      return new Real(((Real*) x)->value + 1.0);
    default:
      throw TypeError("expected an integer or real argument.")
  }
}

increment is now compatible with the Function type and can be stored in mFuncs. You can now call a function of unknown type on arguments of unknown type and you will get an exception if the arguments don't match, or a result of some unknown type if the arguments are compatible.

Most probably you will want to store the function signature as something you can introspect, i.e. dynamically figure out the number and type of arguments that a Function takes. In this case you can make a base Function class with the necessary introspection functions and provide it an operator () to make it look something like calling a regular function. Then you would derive and implement Function as needed.

This is a sketch, but hopefully contains enough pointers to show the way. There are also more type-safe ways to write this code (I like C-style casts when I've already checked the type, but some people might insist you should use dynamic_cast instead), but I figured that is not the point of this question. You will also have to figure out how Value* objects lifetime is managed and that is an entirely different discussion.

Unmeriting answered 8/12, 2021 at 20:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.