Generic functor for functions with any argument list
Asked Answered
T

2

3

I need to implement a functor that takes any (!) function pointer when instantiated, analyses the argument types, stores the pointer and when operator() is called, does something with the pointer. The easiest case being, calling the function with its arguments.

I tried converting the function pointer to something like std::function and I get the error:

error: invalid use of incomplete type ‘struct std::function<void (*)(...)>’
/usr/include/c++/4.6/functional:1572:11: error: declaration of ‘struct std::function<void(*)(...)>’

I am using gcc 4.6.1, compiling with -std=c++0x

This is a minimal example:

#include <functional>

using namespace std;
typedef void (*F_vararg)(...);
class Foo
{
    public:
        template<typename... ARGS> Foo(function<F_vararg(ARGS... args)> f);
        ~Foo(){}
        template<typename... ARGS>void operator()(ARGS... args);
    private:
        F_vararg m_f;
};

template<typename... ARGS> 
Foo::Foo(function<F_vararg(ARGS... args)> f)
{
    m_f = f;
}

template<typename... ARGS>
void Foo::operator()(ARGS... args)
{
    m_f(args...);
}

void func1(double *a1, double *a2, double *b)
{    //do something 
}

int main(void)
{
    Foo func1_functor((std::function<void (*)(...)>)(func1));

    double a1[3] = {2.,3.,4.};
    double a2[3] = {2.2,3.2,4.2};
    double b[3] = {1.5,2.5,3.5};

    func1_functor(a1,a2,b);

    return 0;
}

This does NOT compile... If I don't declare the constructor as a template but with "F_vararg f" as the argument, and adjust the cast in the instantiation accordingly, it works (should it?) but I have no chance (?) of getting any information on the arguments of func1 in the constructor of the functor which I need.

Am I missing something? Is there another way to do that?

Thank you in advance !!!

cheers, Steffen

edit Wow, that was quick! I need that for postponing the execution of functions. The functor (or another class) should be able to decide whether and when to run a function. To decide that it will use the information gathered from the argument list.

I have looked at std::bind but I couldn't think of a way to achieve what I want...

Terrance answered 29/1, 2012 at 0:59 Comment(10)
typedef void (*F_vararg)(...); - that's a C-style vararg function, and you don't need it at all with variadic templates..Polyandrous
What do you need it for?Davey
and yes, there's std::bind after all..Polyandrous
This is currently very type-unsafe. Do you expect it to do type checking to ensure that the parameters match up? If so, you won't be able to just have a type Foo. You'll have to encode the argument types in the functor type, something like Foo<void,double,double,double> to represent a functor which returns a void and takes three doubles as arguments.Debbi
@Cat: What if you wanted to automatically log all parameters, before calling the function, and then log the return value. You could write a wrapper for each function signature, of course, or you could try to write a reusable templated logging wrapper.Gigigigli
@Aaron: The thing is, there will be arbitrary types of functions, func1 just being an example....Terrance
@steffen, that's not quite what I was asking about. I'm asking something a little more subtle. Check out my answer; it works for the example you've given. My answer works for any type of arguments (I left out the return type for simplicity).Debbi
@steffen, you probably must allow the various functor objects to have different types. For example Foo<void(double,double,double)> x(func1) and Foo<void(int,int,int)> y(func2) where func1 and func2 are different functions that take three doubles or three ints respectively. So in this case, x and y are different types; they both come from Foo, but they are different types in the same way that vector<int> and vector<double> are.Debbi
Just as an aside, you may be interested to know that if you have been compiling with g++ and the default -std (prior to C++11) than you had gnu extensions ON, if you now use -std=c++0x this will turn gnu extensions OFF. To leave them on use -std=gnu++0xAcea
@user1131467: Thanks for pointing that out. Who said 'off-topic' is a bad thing ;-)Terrance
G
10

Lose the typedef. A function that takes variable arguments is a specific type, it's not compatible with functions taking typed arguments, and it can't have parameter types specified later.

This should work instead:

template<typename... ARGS> Foo(function<void(*)(ARGS... args)> f);

Of course, what you really need is to template the class, so you can remember what arguments the function requires.

#include <functional>

template<typename... ARGS>
class Foo
{
    std::function<void(ARGS...)> m_f;
public:
    Foo( std::function<void(ARGS...)> f ) : m_f(f) {}
    void operator()(ARGS... args) const { m_f(args...); }
};

template<typename... ARGS>
Foo<ARGS...> MakeFoo(void(*f)(ARGS...)) { return Foo<ARGS...>(f); }

void func1(double *a1, double *a2, double *b)
{    //do something 
}

int main(void)
{
    auto func1_functor = MakeFoo(func1);

    double a1[3] = {2.,3.,4.};
    double a2[3] = {2.2,3.2,4.2};
    double b[3] = {1.5,2.5,3.5};

    func1_functor(a1,a2,b);

    return 0;
}

Demo: http://ideone.com/fnUg2

Gigigigli answered 29/1, 2012 at 1:15 Comment(3)
Thanks, that looks promising. But how will I instantiate the functor in main?Terrance
Thanks @Ben! I corrected the ...args in the operator() definition (compiler error). There are still some compiler errors... But I feel we're getting closer :) --- error: ‘Foo<ARGS>::m_f’ has incomplete type ---- error: no matching function for call to ‘Foo<double*, double*, double*>::Foo(void (&)(double, double*, double*))’ --- candidates are: constexpr Foo<double*, double*, double*>::Foo(const Foo<double*, double*, double*>&) -----Terrance
@steffen: I found and fixed those problems; it's working now.Gigigigli
D
3

To be type-safe, Foo should also encode the type of the arguments. The question doesn't specify the use-case fully, so I'm not sure if it is required to be able to pass 'typeless' Foos around and still have each one somehow remember (at runtime) what the types of its arguments are. Anyway, this code will work with the example given in the qeustion.

#include<utility>

template<typename FunctionType>
struct Foo {
        FunctionType f;
        Foo(FunctionType f_) : f(f_) {}

        template<typename... Args>
        void operator() (Args&&... args) {
                f(  std::forward<Args>(args)... );
        }
};

template<typename FunctionType>
Foo<FunctionType> foo(FunctionType f) {
        return Foo<FunctionType>(f);
}

void func1(double *, double *, double *)
{    //do something 
}


int main() {
        auto x = foo(func1);
        double d[3] = {2,3,4};
        func1(d, d, d);
        x(d, d, d);
}
Debbi answered 29/1, 2012 at 1:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.