Array of functions with different signatures
Asked Answered
T

10

5

I have this classes:

class Foo
{
    ...
};

class Foo1 : public Foo
{
    ...
};

...

class FooN : public Foo
{
    ...
};

Is it possible to have an array of functions with these kind of signatures:

void f1(Foo1*){}
...
void fN(FooN*){}

Is there any change if these functions are non static member functions instead of regular functions? I don't think this will change something.

Thanks!

Trilinear answered 7/9, 2011 at 8:51 Comment(8)
Can you use polymorphism along with a pointer to a Foo? You can't have an array of pointers to functions with different signatures. You could either have them all have a Foo* or all have a void* (I suggest a Foo*).Doubledecker
If I will have a pointer to Foo* I will need to make a cast to FooK inside fK, which I want to avoidTrilinear
You're either going to have to figure out a way to do it polymorphically (as spraff elaborated on more than my first comment), or have some fun pointer casting.Doubledecker
You have incompatible goals. If you want to avoid the dynamic_cast you then you have to not shoehorn different function types into the same container. Separate these types in your design.Tamathatamaulipas
Another way of saying this is "everything in an array must be the same type" so you need to decide and specify what aspects of the FooN types are "the same"Tamathatamaulipas
What you are going to do with such an array? Arrays typically are used for uniform access to the elements. However, you have no runtime information about particular signature of functions packed to the array, and, therefore, you can't even call f[0](..), f[1](..) functions correctly as you don't know the argument type. Or you have some additional info which is missing in your question?Hoofed
@iammilind Why did you removed the tag? Maybe is a feature in the new standard that makes this possible or makes possible a trick to do this.Trilinear
There is no such new feature and the tag is not appropriate. Tagging with categories which you think might magically contain the answer harms the site -- tags are to categorise questions.Tamathatamaulipas
T
0

I found this workaround for this problem:

#include <iostream>
#include <vector>

class Foo
{
};

class Foo1 : public Foo
{
};

class Foo2 : public Foo
{
};

class Foo3 : public Foo
{
};


void f1(Foo1*)
{
    std::cout<<"f1\n";
}

void f2(Foo2*)
{
    std::cout<<"f2\n";
}

void f3(Foo3*)
{
    std::cout<<"f3\n";
}

template<typename T>
void AddPointer(std::vector<typename void (*)(Foo*)>& fPointers, T function)
{
    fPointers.push_back(reinterpret_cast<void (*)(Foo*)>(function));
}

void main()
{
    std::vector<typename void (*)(Foo*)> fPointers;

    AddPointer(fPointers, f1);
    AddPointer(fPointers, f2);
    AddPointer(fPointers, f3);

    Foo1 foo1;
    Foo2 foo2;
    Foo3 foo3;

    fPointers[0](&foo1);
    fPointers[1](&foo2);
    fPointers[2](&foo3);
}
Trilinear answered 7/9, 2011 at 14:28 Comment(3)
You're throwing away type safety needlessly. There are type-safe no-overhead ways of doing this.Tamathatamaulipas
@Tamathatamaulipas I don't need type-safety, I just need to be able to call the functions with different signature from a vector without any cast inside the functions. This is my question, this is the answer for it. If I already have the type information outside the vector why to make another cast inside the function? This implementation is inside a library, the user of it has to know only that if he registers a function with a signature inside a manager, that function will be called with the right parameter and he doesn't need to cast it. A library must have a easy interface because it's users want thatTrilinear
You do need type safety. When you register a function of a particular signature with the manager, the manager should store that function somewhere. When you register a function of a different signature with the manager, the manager should store that function somewhere else, not in the same array/vector.Tamathatamaulipas
T
3

EDIT alternative non-virtual-function-based solution here.

The type void(*)(Foo*) is not convertible to the type void(*)(Bar*) and for good reason.

You should make all your functions take an Interface* argument and all the FooN should derive from Interface

struct Interface {
    virtual ~ Interface () {}
    // ...
};

struct Foo1 : public Interface {
    // ...
};

struct Foo2 : public Interface {
    // ...
};

void f1 (Interface *);
void f2 (Interface *);

void (*functions)(Interface*) [] = {f1, f2};

functions[0] (new Foo1 ());
functions[0] (new Foo2 ());
functions[1] (new Foo1 ());
functions[1] (new Foo2 ());

The implementations of f1, f2 can check at runtime if their argument is a particular implementation by using dynamic_cast and checking for nullptr. The only way to check at compile time is to make f1 and f2 take specific types and not put them in an anonymous array, but invoke them explicitly.


To answer the second part of your question -- yes it DOES matter if they're non-static member functions because the size of the pointer is not constant

Tamathatamaulipas answered 7/9, 2011 at 8:54 Comment(2)
I know I can make an array of functions with same signature, I want to know if it's possible to make an array of function with different signatures - by using some trick or something:). Just Replace Interface with base Foo and you'll have same code as I had in my question:)Trilinear
Replace void f(Foo1*) with void f(Foo*) and you'll have my answer :-) Arrays have to contain objects of the same type. If you can prove that all invocations will contain specific runtime types then the smart thing to do is have one array per specific type.Tamathatamaulipas
H
2

You could use function objects. See the example below on how to do it yourselve. If you like the idea you should have a look at boost.signal/boost.bind and the c++ 0x counterparts.

class Foo1 {};
class Foo2 {};
class Foo3 {};

void func1(Foo1*) {}
void func2(Foo2*) {}
void func3(Foo3*) {}

class FuncObjBase {
public:
    virtual void operator()() = 0;
};

template <class T>
class FuncObj : public FuncObjBase {
public:
    typedef void (*Funcptr)(T*);
    FuncObj(T* instance, Funcptr funcptr) : m_Instance(instance), m_Func(funcptr) {}
    virtual void operator()() { m_Func(m_Instance); }
private:
   T* m_Instance;
   Funcptr m_Func;
};

int main(int argc, char *argv[])
{
    Foo1 foo1;
    Foo2 foo2;
    Foo3 foo3;
    FuncObjBase* functions[3];
    functions[0] = new FuncObj<Foo1>(&foo1, func1);
    functions[1] = new FuncObj<Foo2>(&foo2, func2);
    functions[2] = new FuncObj<Foo3>(&foo3, func3);
    for(unsigned int i = 0; i < 3; i++) {
        (*functions[i])();
    }
    return 0;
}
Hienhieracosphinx answered 7/9, 2011 at 19:19 Comment(2)
Although they all go happily into the array, you can no longer call any of the functions with arguments other than the ones FuncObj bound them to. -1 for missing the point, re-implementing function/bind even though you known about them, and for using very dodgy object ownership logic in the example.Tamathatamaulipas
It's really not about points. Most of the answers here are not type-safe and it distresses me that people don't seem to care. This one is type-safe but isn't useful because it requires binding the argument. Smart pointers have nothing to do with the ownership problems by the way.Tamathatamaulipas
C
1

C++ is a statically typed language, and that includes the types of functions. At every line of code, the C++ compiler must be able to determine whether the function signature is valid and which function (or pointer) to call.

In order to do what you're talking about, you would need to be able to recover the type of the pointer at runtime, based on values put into the array at runtime. And polymorphism is the only type-related thing you can get at runtime. And even that only deals with the type of class. Exactly which function will be called is not up for debate.

The absolute best you can do is use something like an array of boost::variant. You can have a specific set of function prototypes stored in the variant, possibly using a boost::function. However, it would only be a bounded set, not any arbitrary function type. And calling them would be rather difficult, as you would first have to verify that the variant is indeed of the expected function type, then call it.

Another alternative is to use an array of boost::any. Except here, the types could be any function type. Again, calling it will require converting it to one of the expected function types. The problem is compounded since the function types could literally be anything. So you'll have to provide a fallback if it isn't one of the expected function types.

If the list of functions is small and compile-time determined, you could use a boost::tuple as a makeshift "array". However, you have to use template metaprogramming to iterate over them. Of course, if that were the case, you could just use a struct containing function pointers of the appropriate type.

Colonic answered 7/9, 2011 at 9:29 Comment(1)
Is there some reason for the -1?Colonic
B
1

You can do this in C++11 with Variadic Templates. Check my answer which is similar to what you want but with maps at: https://mcmap.net/q/335265/-using-a-stl-map-of-function-pointers

Bladder answered 20/11, 2015 at 22:58 Comment(0)
H
0

You could use function objects.

For example Boost.Signal or the ones from C++0x / TR1

Hienhieracosphinx answered 7/9, 2011 at 9:2 Comment(6)
No, the complete type of std::function is, for example, std::function<void(Foo*)>. This polymorphic function wrapper quite rightly has an exact type correspondence with equivalent ordinary functions.Tamathatamaulipas
@Tamathatamaulipas this makes them ideal to use in a array of function objects or?Hienhieracosphinx
You can't but a std::funcion<void(Foo*)> in the same array as a std::funcion<void(Bar*)> because they're objects of different types.Tamathatamaulipas
@Tamathatamaulipas with std::bind you canHienhieracosphinx
I think you're misunderstanding what std::bind is for. I don't think it can map Foo* to Bar* in this way (it binds a particular Foo* or Bar* to the function, yielding a std::function<void()>) but even if it could do that, it isn't what is required.Tamathatamaulipas
@Tamathatamaulipas I thought it is what was wanted -> put the functions in an array. It was not required that the array needs to be of any certain type for example std::function<void(Foo1*)>Hienhieracosphinx
A
0

You could make the functions f1 through fN members of their particular argument classes, name them the same and use virtual dispatch to call the right functions. Then you would just have to fill pointers to the member functions into the array.

Amalburga answered 7/9, 2011 at 9:11 Comment(0)
H
0

What you want is covariant argument types. This is not supported in C++, because it breaks type safety. To better understand this, let's take a simple example:

struct Vehicle {};
struct Tricycle : Vehicle {};
struct Tank : Vehicle {};

void drive(Vehicle const & b) { ... }
void giveToChild(Tricycle const & b) { ... }

Here we have a simple type hierarchy, as well as two functions taking a reference to respectively the base class and one of the derived class. Now, if what you ask for were allowed, we could do the following:

typedef void (*funcPtr)(Vehicle const &);

funcPtr = &giveToChild; // this is not allowed
funcPtr(Tank());        // oops, I just gave a tank to my child!

The language could implement some sort of runtime type verification, but this is not how C++ works.

However, the reverse conversion (contravariance) could be allowed without any issues (actually, C# delegates allow it), but is not possible in C++ for some reasons I am not aware of. Here is a sample of what it would allow:

typedef void (*funcPtr)(Tricycle const &);

funcPtr = &drive;    // this could be allowed, but is not (in C++)
funcPtr(Tricycle()); // I can only drive a tricycle, but that's ok since it's a
                     // vehicle and I know how to drive all vehicles

So basically, what you are trying to achieve is not possible without resorting to forwarding functions that would check the type of the arguments before calling the original functions:

void forwardFN(Foo * f)
{
    FooN * instance = dynamic_cast<FooN *>(f);

    if (instance) fN(instance);
    else throw type_exception();
}
Helle answered 7/9, 2011 at 10:5 Comment(0)
T
0

I found this workaround for this problem:

#include <iostream>
#include <vector>

class Foo
{
};

class Foo1 : public Foo
{
};

class Foo2 : public Foo
{
};

class Foo3 : public Foo
{
};


void f1(Foo1*)
{
    std::cout<<"f1\n";
}

void f2(Foo2*)
{
    std::cout<<"f2\n";
}

void f3(Foo3*)
{
    std::cout<<"f3\n";
}

template<typename T>
void AddPointer(std::vector<typename void (*)(Foo*)>& fPointers, T function)
{
    fPointers.push_back(reinterpret_cast<void (*)(Foo*)>(function));
}

void main()
{
    std::vector<typename void (*)(Foo*)> fPointers;

    AddPointer(fPointers, f1);
    AddPointer(fPointers, f2);
    AddPointer(fPointers, f3);

    Foo1 foo1;
    Foo2 foo2;
    Foo3 foo3;

    fPointers[0](&foo1);
    fPointers[1](&foo2);
    fPointers[2](&foo3);
}
Trilinear answered 7/9, 2011 at 14:28 Comment(3)
You're throwing away type safety needlessly. There are type-safe no-overhead ways of doing this.Tamathatamaulipas
@Tamathatamaulipas I don't need type-safety, I just need to be able to call the functions with different signature from a vector without any cast inside the functions. This is my question, this is the answer for it. If I already have the type information outside the vector why to make another cast inside the function? This implementation is inside a library, the user of it has to know only that if he registers a function with a signature inside a manager, that function will be called with the right parameter and he doesn't need to cast it. A library must have a easy interface because it's users want thatTrilinear
You do need type safety. When you register a function of a particular signature with the manager, the manager should store that function somewhere. When you register a function of a different signature with the manager, the manager should store that function somewhere else, not in the same array/vector.Tamathatamaulipas
M
0

I would suggest using a std::tuple instead of a std::array or C-array. Using a std::tuple you can store elements of different types.

Motherofpearl answered 8/9, 2011 at 2:49 Comment(0)
T
0

Here's a generic approach which is type-safe and forces client code to be correct.

class Manager {
public:

    typedef int /* or whatever */ ID;

    template <typename Function>
    static void save (Function * f, ID id) {
        functions <Function> () .add (id, f);
    }

    template <typename Function>
    static Function * get (ID id) {
        return functions <Function> () .get (id);
    }

private:

    template <typename Function>
    class FunctionStore {
    public:

         void add (Function *, ID);
         Function * get (ID);

    private:
         // or vector, if you know ID is int.
         std :: map <ID, Function *> m_functions;
    };

    // type_index is C++11 but you can implement it in C++03.
    // void* here is unpleasant but you can improve it, RAII it.
    typedef std :: map <std :: type_index, void *> Store;
    static Store m_store;

    template <typename Function>
    FunctionStore <Function> & functions () {
        FunctionStore <Function> * fs;

        Store :: iterator i = m_store .find (typeid Function);

        if (m_store .end () == i) {
            fs = new FunctionStore <Function> ();
            m_store [typeid Function] = fs;
        }
        else {
            // This void* cast is OK because it's internally controlled
            // and provably correct.
            // We don't have to trust the library to not abuse it.
            fs = static_cast <FunctionStore<Function>*> (i -> second);
        }

        return *fs;
    }
};

// In the library

void foo1 (Foo *);
void bar1 (Bar *);
void foo2 (Foo *);
void bar2 (Bar *);

void init () {
    Manager :: save (foo1, 1);
    Manager :: save (foo2, 2);
    Manager :: save (bar1, 1);
    Manager :: save (bar2, 2);

    Manager :: get <void(Foo*)> (1) (new Foo ()); // OK, calls foo1
    Manager :: get <void(Foo*)> (1) (new Bar ()); // Will not compile
    Manager :: get <void(Bar*)> (2) (new Bar ()); // OK, calls bar2
}

If you don't want the overhead of the lookup in m_store (and/or want to avoid the void in Manager::Store) you can make Manager itself a template class, the downside is you now have to watch out for your static m_store definitions. This is OK if you know the clients will only use a given set of Function signatures.

void init () {
    Manager <void(Foo*)> :: save (foo1, 1);
    Manager <void(Foo*)> :: save (foo2, 2);
    Manager <void(Foo*)> :: save (bar1, 1); // Won't compile
    Manager <void(Bar*)> :: save (bar1, 1);
    Manager <void(Bar*)> :: save (bar2, 2);

    Manager <void(Foo*)> :: get (1) (new Foo ()); // OK, calls foo1
    Manager <void(Foo*)> :: get (1) (new Bar ()); // Will not compile
    Manager <void(Bar*)> :: get (2) (new Bar ()); // OK, calls bar2
}

The init function illustrates the crucial point I've been making in comments on other posts: if you know which types you're going to invoke a function with then you know which collection of functions to fetch from. There's no need to try to cram them all together and it only harms your type safety to do so.

Tamathatamaulipas answered 8/9, 2011 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.