Search code examples
c++functionstructvariadic-templateswrapper

How can you move a parameter pack and function pointer into a struct?


I've been running around stack overflow and google for hours now. I can't seem to wrap my head around how to store a function pointer and it's arguments in a wrapper struct.

The following class wraps a function (in this case a packaged task with templated returntypes and arguments). The main part of the wrapper is listed below.

EDIT: The reason why I don't use std::function is because the wrapper (ThreadTask) wraps a std::packaged_task that gets moved into a threadsafe deque. Packaged_task is move-construbable and does not support copy, which std::function do. Therefore I make a custom wrapper.

The compiler gives an error and states that the function doesn't take 0 arguments. But I don't know how to pass along the arguments / store them.

class ThreadTask {
    struct BaseInterface {

        virtual void Call() = 0;
        virtual ~BaseInterface() {};
    };
    std::unique_ptr<BaseInterface> mImplementation;

    template <typename F, typename...Args>
    struct BaseFunc : BaseInterface {
        F func;

        BaseFunc(F&& f, Args... args) : func(std::move(f)) {}
        void Call() { func(); }
    };
};

I've tried unpacking the arguments in different ways, but I can't seem to make it work. I think I should forward them some how by std::forward, but i can't figure out were to store the parameters


Solution

  • I recommend using std::function but if you don't want to use that for some reason, you could extend your current class to store the arguments in std::tuple and then use std::apply to "unpack" and call the function with the stored arguments.

    Example:

    class ThreadTask {
    public:
        struct BaseInterface {
            virtual ~BaseInterface() = default;
            virtual void operator()() = 0;
        };
    
        std::unique_ptr<BaseInterface> mImplementation;
    
        template <typename F, typename... Args>
        struct BaseFunc : BaseInterface {
            BaseFunc(F f, Args... args)
                : func(std::move(f)), m_args{std::move(args)...} {}
    
            void operator()() override {
                std::apply(func, m_args); 
            }
    
            F func;
            std::tuple<Args...> m_args;
        };
    };
    

    Usage example:

    void foo(int x, double y) {
        std::cout << "got " << x << " and " << y << '\n'; 
    }
    
    int main() {
        ThreadTask::BaseFunc bf{&foo, 12, 3.14159};
    
        bf();
    }
    

    Note: I changed Call() to operator()() which seemed more appropriate and made the members public for the demo.

    Demo