Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you create a vector of function pointers that can take different arguments?

Tags:

c++

vector

I'm trying to learn how to store functions (or rather pointers to functions) in std::vector. I have this code:

#include <iostream>
#include <vector>

void x(int i)
{
    std::cout << "x is " << i << std::endl;
}

void y(int i, int j)
{
    std::cout << "y is " << i << " and " << "j" << std::endl;
}

int main()
{
    std::vector<void(*)(int)> V;

    V.push_back(x);

    V[0](1);

    return 0;
}

This works perfectly but the problem is that I can't push_back function y into the same vector since it takes 2 integers instead of one.

What should I do to store both functions in the same vector?

like image 543
Nathaniel G.M. Avatar asked Jan 30 '23 17:01

Nathaniel G.M.


1 Answers

There is no good way to do what you want, but you can do it.

Write an augmented variant (std or boost or hand rolled tagged typesafe union) that supports implicit cast-from with exception if you get the type wrong (feel free to support conversion between types if desired). Call this poly_arg<Ts...>

Write a type eraser that takes an arg type as a template parameter. It then takes a function pointer at construction, and type erases calling it with a vector of just the right length of arguments. (Or a function object and an arg count range). It then has a vararg operator() that forwards its arguments into a vector of its arg type, then tries to call using the above type erasure. If the wrong number of arguments is passed, it throws an exception. Call this vararg_func<T>.

Store a vector of vararg_func<poly_arg<int, double, std::string>> (list of 3 types is just an example). This can store void(*)(int) and void(*)(int,int) and void(*)(std::string, double, int) and void(*)() etc, and you can invoke it. If you get the argument count wrong, you get an exception (from vararg func). If you get an argument type wrong, exception (from poly arg). If you pass an incompatible function pointer, compile error at push_back (which is great!)

If you only need to support int args you can skip poly_arg and store vararg_func<int> instead.

I think this is a bad plan.

You very very rarely want to treat functions with different numbers and types of arguments uniformly. The few legitimate cases are best handled with two coupled type erasing systems (like efficient massive customization point tables with non-uniform signatures) that hide the type unsafety internally.

Instead this plan matches your requirements, which forces type unsafety in its interface and pollutes your code with "dunno, maybe it will work" calls.

If you want help implementing those type erasers, realize that I both know how to write them and how they solved your problem and in my opinion they are a really bad idea. If that fails to deter you, go and learn about type erasure in C++ and value-type polymorphism and how std::function works. Try to write a toy std::function. Play with a "view-only" and "move-only" version. Try a zero-allocation with bounded function object size. That should take a few weeks or years.

Now write some more simple cases, like printing to an ostream. Get good enough at it. At which point vararg_func shoukd be challenging but doable; try it. If it fails, ask SO to help, including your attempt.

poly_arg should be easy in comparison.

like image 80
Yakk - Adam Nevraumont Avatar answered Feb 08 '23 17:02

Yakk - Adam Nevraumont