[C++0x] Variable Template-Parameter-Liste "umdrehen" [gelöst]



  • Hallo zusammen,

    ich will gerade eine Funktion schreiben, die eine Funktion annimmt und sie in einem neuen Thread mit den übergebenen Parametern startet. Das Ganze soll mit einem QThread passieren (da std::thread ja im MinGW noch nicht implementiert ist und ich nicht auch noch boost dazulinken will). Das meiste kompiliert bisher auch schon, aber ich will jetzt noch die Funktionsparameterliste "umdrehen", da ich sie beim Casten von void-Pointern nochmal umdrehen muss. Das bedeutet dann zum Beispiel, dass aus <int, std::string, char *, std::runtime_error> <std::runtime_error, char *, std::string, int> werden muss. Das habe ich bisher so versucht:

    Die aufgerufene Funktion speichert zuerst die Adressen der Parameter rückwärts und dann den Funktionszeiger in einem globalen void*-Vector. Das wird natürlich mit einem QMutex gelockt. Die Funktion run() des Threads ruft die Funktion ExecFuncFromVector mit dem Vector und dem Funktionszeiger auf. Diese versucht, die Parameter umzudrehen mithilfe der (Un)-Inverted-Klassen (was nicht kompiliert). Diese sollten dann die Funktion AddCastedTypesToFunctionCall() mit der umgekehrten Parameterliste aufrufen, damit sie die void-Pointer casten und richtig herum an die Parameterliste anhängen kann. Am Schluss werden die Pointer dereferenziert, die Funktion aufgerufen und der thread_exec()-Funktion mitgeteilt, dass sie sich beenden kann.

    (includes fehlen, die Funktionen wait() und GetParameterCount(), die die gegebene Anzahl an Sekunden warten bzw. die Anzahl ihrer Parameter zurückgeben, sind hier nicht aufgeführt.

    template<typename Ret, typename... Params> class Thread : public QThread
    {
        static void CallAndSetFinished(Ret (*func)(Params...), Params... params)
        {
            call_function_information.clear();
    
            //we have to copy the parameters before we unlock the mutex
            //because otherwise the calling function could clean up the stack
            has_finished_mutex.lock();
            has_finished = true;
            has_finished_mutex.unlock();
    
            func(params...);
        }
    
        template<size_t vec_pos, typename... Dummy_Types> class TypeContainer
        {
            public:
    
            template<typename... Args> static void AddCastedTypesToFunctionCall(Ret (*func)(Params...),
                                                                              std::vector<void *> const &vec,
                                                                              Args*... params)
            {
                Thread<Ret, Params...>::CallAndSetFinished(func, *params...);
            }
        };
    
        template<size_t vec_pos, typename NextType, typename... FollowingParamTypes> class TypeContainer<vec_pos, NextType, FollowingParamTypes...>
        {
            public:
    
            template<typename... Args>
                static void AddCastedTypesToFunctionCall(Ret (*func)(Params...),
                                                         std::vector<void *> const &vec,
                                                         Args*... args)
            {
                Thread<Ret, Params...>::TypeContainer<vec_pos+1, FollowingParamTypes...>
                                         ::AddCastedTypesToFunctionCall(func,
                                                                                   vec,
                                                                                   reinterpret_cast<NextType *>(vec[vec_pos]),
                                                                                   args...);
            }
        };
    
        //das mit den Dummy_Args habe ich versucht, weil eine Spezialisierung
        //template<> class Uninverted<> ebenfalls nicht funktioniert hat.
        //die Dummy_Args werden aber nicht verwendet  
        template<typename... Dummy_Args> class Uninverted  
        {
            public:
            template<typename... InvTypes> class Inverted
            {
                static void Call(Ret (*func)(Params...), std::vector<void *> const &vec)
                {
                    Thread<Ret, Params...>::TypeContainer<0, InvTypes...>::AddCastedTypesToFunctionCall(func, vec);
                }
            };
        };
    
        template<typename First, typename... Rest> class Uninverted<First, Rest...>
        {
            public:
            template<typename... InvTypes> class Inverted
            {
                static void Call(Ret (*func)(Params...), std::vector<void *> const &vec)
                {
                    Thread<Ret, Params...>
                    ::Uninverted<Rest...>
                    ::Inverted<First, InvTypes...>
                    ::Call(func, vec);
                }
            };
        };
    
        static void ExecFuncFromVector(Ret (*func)(Params...), std::vector<void *> const &vec)
        {
            Thread<Ret, Params...>::Uninverted<Params...>::Inverted<>::Call(func, vec);
        }
    
        public:
        virtual void run()
        {
            //after the params, there is the function address
            Ret (*call_func)(Params...)
                = static_cast<Ret(*)(Params...)>(call_function_information[call_function_information.size()-1]);
    
            ExecFuncFromVector(call_func, call_function_information);  //executes call_func with the elements from the vector starting at <1>
            //TODO
        }
    };
    
    template<typename VecType> void AddParametersReverseToVector(std::vector<VecType> &vec)
    {
        //nothing
    }
    
    template<typename VecType, typename First, typename... Args> void AddParametersReverseToVector(std::vector<VecType> &vec,
                                                                                                   First first,
                                                                                                   Args... args)
    {
        AddParametersReverseToVector(vec, args...);
        vec.push_back(reinterpret_cast<VecType>(first));
    }
    
    template<typename Ret, typename... Params> Thread<Ret, Params...> *thread_exec(Ret (*func)(Params...),
                                                                                         Params... params)
    {
        Thread<Ret, Params...> *new_thread = ManagedNew(Thread<Ret, Params...>());
        thread_func_exec_mutex.lock();
        //TODO
        call_function_information.reserve(GetParameterCount(params...) + 1);
    
        //the vector the argument addresses in a reverse order and at last the function address
        AddParametersReverseToVector(call_function_information, &params...);
        call_function_information.push_back(reinterpret_cast<void *>(func));
    
        //won't be set at the moment because the thread isn't already running
        //and other thread_execs are blocked by the other mutex
        has_finished = false;
    
        *new_thread->start();
    
        ///how it works:
        //to call the function, we firstly get its address from the vector,
        //then we put the variable arguments in a reverse order
        //because we do make it reverse when casting the void pointers from the std::vector to the correct pointer type
        //at last, we dereference the pointers, copy them and then set has_finished to true so that this function can return
    
        while(true)  //wait until the thread has finished copying the data
        {
            has_finished_mutex.lock();
            bool tmp_finished = has_finished;  //copy it because we do have to unlock the has_finished_mutex in all cases
            has_finished_mutex.unlock();
            if(tmp_finished) break;
            wait(0.000001);
        }
    
        thread_func_exec_mutex.unlock();
        return new_thread;
    }
    

    Fehler:

    69:33: error: expected primary-expression before ',' token
    69:43: error: expected primary-expression before '...' token
    69:43: error: expected ';' before '...' token
    77:71: error: expected primary-expression before '>' token
    77:72: error: '::Call' has not been declared
    

    Ich könnte da jetzt versuchen, noch etwas mit SFINAE oder so zu machen, aber ich habe eigentlich nicht so viel Lust auf noch mehr Template-Gefrickel.

    mfg



  • Ich denke, das ist/sieht viel zu kompliziert aus, für das, was es eigentlich tun soll. Icb kenne leider QThread nicht, aber ich glaube, das wichtige davon kann ich mir zusammenreimen. Ich gehe mal davon aus das QThread polymorph ist und einen virtuellen Dtor hat. Bzgl Speichern von Parametern ist eigentlich std::tuple und std::make_tuple sehr praktisch.

    template<class FuncType, class ParamTuple>
    class MyThread : public QThread
    {
      FuncType fun;
      ParamTuple pt;
    public:
      MyThread(FuncType f, ParamTuple p)
      : fun(forward<FuncType>(f)
      , pt(forward<ParamTuple>(p)
      {}
    
      void run() {
        typedef typename make_indices<tuple_size<ParamTuple>::value>::type idx;
        invoke_tuple_args(fun,pt,idx());
      }
    };
    
    template<class Func, class... Params>
    auto_ptr<QThread> make_thread(Func f, Params&&... p)
    {
      typedef decltype(make_tuple(forward<Params>(p)...)) pt_type;
      typedef MyThread<Func,pt_type> derived_type;
      auto_ptr<QThread> ap;
      ap.reset(new derived_type(move(f),make_tuple(forward<Params>(p)...)));
      return ap;
    }
    

    Das einzige Problem, was übrig bleibt, ist die Implementierung von dem, was in MyThread<>::run aufgerufen wird. Hier ein paar Anhaltspunkte mehr

    template<int... I> struct indices {};
    
    template<int N> struct make_indices {...};
    
    template<class F, class P, int... I>
    void invoke_tuple_args(F && f, P && p, indices<I...>)
    {
      f(std::get<I>(p)...);
    }
    

    wobei make_indices<N> dann ein typedef indices<0,1,...,N-1> type; enthält. Das erfordert dann ein bissel Metaprogrammierung, ist aber möglich.

    kk



  • Hey, das sieht ja so aus, als würdest du schon 2 Jahre lang C++0x programmieren 👍 😉
    Ich musste mir deinen Code ein paar mal durchlesen, bis ich ihn einigermaßen verstanden habe. Jetzt würde ich aber gerne noch wissen:
    1. Das heißt dann ja im Prinzip, dass ich nur noch das typedef in make_indices implementieren muss?
    2. Warum braucht man da ein struct indices mit einer int-Liste und kann die ints nicht gleich so übergeben?
    3. Kann man einen auto_ptr zurückgeben? Ich dachte immer, dazu seien shared_ptr da.

    Wie man das typedef hinbekommt, ist mir jetzt noch nicht gleich eingefallen, aber es müsste ja irgendwie über Rekursion gehen.



  • So, da hätten wir es ja:

    template<int... List> struct indices{};
    
    template<int count, int N, int... NList> struct indices_helper;
    template<int N, int... NList> struct indices_helper<N, N, NList...>
    {
        typedef indices<NList...> type;
    };
    
    template<int count, int N, int... NList> struct indices_helper
    {
        typedef typename indices_helper<count+1, N, NList..., count>::type type;
    };
    
    template<int N> struct make_indices
    {
        typedef typename indices_helper<0, N>::type type;
    };
    
    template<class F, class P, int... I>
    void invoke_tuple_args(F && f, P && p, indices<I...>)
    {
        f(std::get<I>(p)...);
    }
    
    template<class FuncType, class ParamTuple>
    class Thread : public QThread
    {
        FuncType fun;
        ParamTuple pt;
    public:
        Thread(FuncType f, ParamTuple p)
        : fun(std::forward<FuncType>(f))
        , pt(std::forward<ParamTuple>(p))
        {}
    
        void run()
        {
            typedef typename make_indices<std::tuple_size<ParamTuple>::value>::type idx;
            invoke_tuple_args(fun,pt,idx());
        }
    };
    
    template<class Func, class... Params>
    std::shared_ptr<QThread> make_thread(Func f, Params&&... p)
    {
        typedef decltype(make_tuple(std::forward<Params>(p)...)) pt_type;
        typedef Thread<Func,pt_type> derived_type;
        std::shared_ptr<QThread> ap;
        ap.reset(new derived_type(std::move(f), std::make_tuple(std::forward<Params>(p)...)));
        ap->start();
        return ap;
    }
    


  • Das mit dem make_indices geht auch einfacher...

    template<int N, int... Tail>
    struct make_indices : make_indices<N-1,N-1,Tail...> {};
    
    template<int... Tail>
    struct make_indices<0,Tail...> {
      typedef indices<Tail...> type;
    };
    

    🙂

    BTW: Dadurch, dass hier make_tuple verwendet wird, ist es möglich, genau zu kontollieren, wie die Parameter gespeichert werden. Per default werden sie kopiert. Du kannst aber über std::ref und std::cref das speichern einer Referenz erzwingen:

    using std::ref;
    int i = 23;
    auto_ptr<QThread> t1 = make_thread(&myfunc, i); // i wird kopiert
    auto_ptr<QThread> t2 = make_thread(&myfunc, ref(i)); // Referenz auf i wird gespeichert
    

    Toll, oder? 😃

    kk



  • thx



  • krümelkacker schrieb:

    Toll, oder? 😃

    Stimmt, allerdings würde ich gerne eher danach gehen, welche Parameter die Funktion annimmt. Wobei ich wahrscheinlich sowieso Pointer übergeben werde, falls ich Referenzsemantik brauche.


Anmelden zum Antworten