'C++ handle changing types in template

I'm writing a simple event loop, where I have lambda's to be queued for execution a little like so:

eventloop->enqueue(3000, []{
    // do some work
});

The queued events are stored in a template<typename T> class Job { /* fields */ }

This ensures all lambda's that I enqueue will have return type T. But I want to be able to let the lambda's have any set of parameter types, without having to create a new event loop and add that particular lambda to its type with a parameter pack like so: template<typename T, typename... Ts> class Job { /* fields */ } this will mean all lambda's will have same set of parameters.

Is there any way to achieve what I want without involving something like boost::any (I am limited to C++11 hence boost and not std::any)

test.cpp

#include <thread>
#include <iostream>

#include "ev_loop/ev_loop.h"

int main() {
    ev::EvLoop<void> a(3);
    a.enqueue(ev::Job<void>([]() {
        std::cout << "Running a task" << std::endl;
    }));

    // just for illustration
    std::this_thread::sleep_for(std::chrono::hours(1));
    return 0;
}

ev_loop.h

#ifndef EV_LOOP_H
#define EV_LOOP_H

#include <mutex>
#include <memory>
#include <unordered_map>
#include <condition_variable>
#include <boost/lockfree/spsc_queue.hpp>

namespace ev {
    template<typename T>
    class Job {
        public:
            Job(std::function<T()> func): func(func) {}
            T Run();
        private:
            std::function<T()> func;
    };

    typedef uint32_t u32;

    template<typename T>
    using WorkQueue = boost::lockfree::spsc_queue<Job<T>, boost::lockfree::capacity<128>>;

    template<typename T>
    struct WorkerOptions {
        u32 id;
        std::shared_ptr<WorkQueue<T>> queue;
        std::shared_ptr<std::mutex> mtx;
        std::shared_ptr<std::condition_variable> cv;
    };


    template<typename T>
    class Worker {
        public:
            Worker() = default;
            Worker(WorkerOptions<T> options): work_queue_mutex(*options.mtx), id(options.id), queue(options.queue), cv(options.cv) {}
            void run();
        private:
            u32 id;
            std::shared_ptr<WorkQueue<T>> queue;
            std::mutex &work_queue_mutex;
            std::shared_ptr<std::condition_variable> cv;
    };

    template<typename T>
    class EvLoop {
        public:
            EvLoop(u32 num_workers);
            void enqueue(Job<T> j);
        private:
            std::mutex worker_mtx;
            std::unordered_map<u32, WorkerOptions<T>> work_queues;
    };
}

#endif

ev_loop.cpp

#include <mutex>
#include <thread>
#include <condition_variable>

#include "ev_loop.h"

using namespace ev;

template<typename T>
EvLoop<T>::EvLoop(u32 num_workers) {
    for (u32 i = 0; i < num_workers; i++) {
        this->work_queues[i] = WorkerOptions<T>{i+1, std::make_shared<WorkQueue<T>>(), std::make_shared<std::mutex>(), std::make_shared<std::condition_variable>()};
        WorkerOptions<T> q = this->work_queues[i];
        Worker<T> temp(q);
        std::thread(&Worker<T>::run, temp).detach();
    }
}

template<typename T>
void EvLoop<T>::enqueue(Job<T> j) {
    // for example only
    this->work_queues[0].queue->push(j);
    this->work_queues[0].cv->notify_one();
}

template<typename T>
void Worker<T>::run() {
    while (true) {
        std::unique_lock<std::mutex> lock(work_queue_mutex);
        cv->wait(lock, [this]{return !queue->empty();});
        queue->consume_one([&](Job<T> item) {
            item.Run();
            // give result back to client through a channel or something
        });
        lock.unlock();
    }
}

template<typename T>
T Job<T>::Run() {
    return func();
}

template class EvLoop<void>;


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source