Container of Different Functors

馋奶兔 提交于 2020-01-03 17:23:32

问题


I am trying to figure out a way to have a container of functors so that I can pass a value into the functors and have it be modified however I am having trouble allowing the functors to not be restricted in what types they can be passed and the amount of arguments they can take.

My actual use for this is that I have a series of functors which all change a 3D vector in some way based on the input. By being able to store these functors in a container I can manipulate the order in which they are called in and end up with a different resulting vector by iterating over the container passing each functor the vector. I'm using these vectors to determine the position, colour, acceleration, etc. of a particle. So ultimately I can create vastly different particle effects by taking this modular approach and eventually I can have the functor order be defined by a file during runtime. That's my final goal :)

The only way I have made this work is with inheritance and a bunch of void pointers which makes the code extremely hard to follow, debug and use.

Here's my code so far that will hopefully demonstrate what I am trying to do better than what I have typed above. Please be aware I am highly out of my comfort zone so this code may be horrible and make some of you gurus want to beat me with a stick.

#include <iostream>
#include <vector>

//the base functor class
struct Functor {
    //an integer so we can tell how many arguments the functor takes
    unsigned num_arguments;
    Functor() : num_arguments(0) {}

    //I'm making the arguments default to 0 so the compiler doesn't complain about not giving enough arguments
    virtual void operator()(void* R, void* A1 = 0, void* A2 = 0, void* A3 = 0) = 0;
};

template<typename R, typename A1>
struct Double : public Functor {
    Double() { num_arguments = 1; }
    void operator()(void* i, void* a, void*, void*) {
        //having to cast everything out of void pointers so it can be used
        A1& first = *static_cast<A1*>(a);

        *static_cast<R*>(i) = first * 2;
    }
};

template<typename R, typename A1, typename A2>
struct Sub : public Functor {
    Sub() { num_arguments = 2; }
    void operator()(void* i, void* a, void* b, void*) {
        //having to cast everything out of void pointers so it can be used
        A1& first = *static_cast<A1*>(a);
        A2& second = *static_cast<A2*>(b);

        *static_cast<R*>(i) = first - second;
    }
};

int main() {
    //our container for the functors
    std::vector<Functor*> functors;
    functors.push_back(new Double<int, int>);
    functors.push_back(new Sub<int, int, int>);

    for(int i = 0; i < functors.size(); ++i) {
        int result;
        int first = 1, second = 2;
        Functor& f = *functors[i];

        if(f.num_arguments == 1) {
            f(&result, &first);
        } else if(f.num_arguments == 2){
            f(&result, &first, &second);
        }

        std::cout << result << std::endl;
    }

    Functor* float_sub = new Sub<float, float, float>;
    float result;
    float first = 0.5f, second = 2.0f;
    (*float_sub)(&result, &first, &second);
    std::cout << result << std::endl;

    functors.push_back(float_sub);

    //The functors vector now contains 3 different types of functors:
    //One that doubles an integer
    //One that subtracts two integers
    //One that subtracts two floats

    std::cin.get();
    return 0;
}

Side note: I am expecting some people to tell me to use so-and-so from the boost libraries. Whilst I appreciate knowing that option is there I would still like to know a better way to implement this without any outside libraries as this is something of a learning exercise for myself.

Edit

Okay so having learnt of stdarg and boost::any I think I can see a way to make this work nicely and am trying it out :)


Solution 2

Okay I have reworked the code using the boost::any and cstdarg for what I think is a better solution. This doesn't use void pointers and doesn't restrict the amount of arguments the functors can have. The newer code also allows you to pass in by value, using void pointers everything had to be by reference which would cause problems trying to do: Sub(&result, 1, 1)

#include <iostream>
#include <vector>
#include <cstdarg>
#include <boost\any.hpp>

struct Functor {
    unsigned num_arguments;
    Functor() : num_arguments(0) {}

    virtual void operator()(boost::any, ...) = 0;
};

template<typename R, typename A1>
struct Double : public Functor {
    Double() { num_arguments = 1; }
    void operator()(boost::any r, ...) {
        R* out = boost::any_cast<R*>(r);

        va_list args;
        va_start(args, r);
        A1 first = va_arg(args, A1);
        va_end(args);

        *out = first * 2;
    }
};

template<typename R, typename A1, typename A2>
struct Sub : public Functor {
    Sub() { num_arguments = 2; }
    void operator()(boost::any r, ...) {
        R* out = boost::any_cast<R*>(r);

        va_list args;
        va_start(args, r);
        A1 first = va_arg(args, A1);
        A2 second = va_arg(args, A2);
        va_end(args);

        *out = first - second;
    }
};

int main() {
    std::vector<Functor*> functors;

    functors.push_back(new Double<int, int>);
    functors.push_back(new Sub<int, int, int>);
    functors.push_back(new Sub<int, float, float>);

    int result = 0;

    for(int i = 0; i < functors.size(); ++i) {
        (*functors[i])(&result, 2, 2);
        std::cout << result << std::endl;
    }

    std::cin.get();
    return 0;
}

and now I finally have the privilege to upvote :D


回答1:


Have you thought about using variadic argument lists from the stdarg library? I am not sure it is a better solution, but it is different. For example:

#include <vector>
#include <cstdarg>
#include <iostream>

using namespace std;

template< typename type >
void sub( void* out, ... ){
    // Initialize the variadic argument list.
    va_list args;
    va_start( args, out );

    // Extract our arguments.
    type lhs = va_arg( args, type );
    type rhs = va_arg( args, type );

    // Close the variadic argument list.
    va_end( args );

    // Function logic goes here.
    *(static_cast<type*>(out)) = lhs - rhs;
}

int main( void ){
    typedef void (*functor)( void* out, ... );  // Function type.
    typedef vector< functor > FunctorList;      // Function list type.

    FunctorList fList;
    fList.push_back( &sub<int> );

    int diff;
    fList[0]( &diff, 3, 5 );
    cout << diff << endl;

    return 0;
}



回答2:


I did not realize you specifically wanted function objects (should have clicked from your usage of "functor" and not "function pointer"). Anyways, here is my previous answer using function objects instead. It works just the same:

#include <vector>
#include <cstdarg>
#include <iostream>

using namespace std;

struct FunctorBase {
    virtual void operator()( void* out, ... ) = 0;
}; // end FunctorBase

template< typename T >
struct Subtract : public FunctorBase {
    void operator()( void* out, ... ){
        // Initialize the variadic argument list.
        va_list args;
        va_start( args, out );

        // Extract our arguments
        T lhs = va_arg( args, T );
        T rhs = va_arg( args, T );

        // Close the variadic argument list.
        va_end( args );

        // Function logic goes here.
        *(static_cast<T*>(out)) = lhs - rhs;
    }
};

int main( void ){
    typedef vector< FunctorBase* > FunctorList; // Function list type.

    FunctorList fList;
    fList.push_back( new Subtract<int>() );

    int diff;
    (*fList[0])( &diff, 3, 5 );
    cout << diff << endl;

    return 0;
}


来源:https://stackoverflow.com/questions/8581902/container-of-different-functors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!