Is this thread pool implementation OK?
~ThreadPool() = default;

We need to call stop() in the destructor!

template&lt;typename Func, typename... Args&gt;
decltype(auto) add_task(Func&amp;&amp; func, Args... args) {
    using ReturnType = decltype(func(args...));

For perfect forwarding we need to use &amp;&amp; for args:
template&lt;typename Func, typename... Args&gt;
decltype(auto) add_task(Func&amp;&amp; func, Args&amp;&amp;... args) {
    using ReturnType = decltype(func(std::forward&lt;Args&gt;(args)...));


ThreadPool(int number_of_workers) {

We need to check that this number is greater than zero. Also it might be better to use an unsigned int to match the result of std::thread::hardware_concurrency().

void thread_func() {
    while(true) {
        std::unique_lock lk{pool_mutex};
        if (task_queue.empty() &amp;&amp; stopped) {
            break;
        }
        ...

I'm not sure that we should wait for the task_queue to be empty before stopping. I'd suggest that stop() should prevent threads taking any more tasks from the queue (i.e. provide a way to &quot;cancel&quot; tasks that haven't started executing yet).
If we wait for the queue to be empty when calling stop(), we're waiting for all the tasks to complete, so calling stop() doesn't really do much. (The calling code is already in control of whether or not it adds more tasks and waits for futures).
        if (task_queue.empty()) {
            cond.wait(lk, [this]() { return !task_queue.empty() || stopped; });
        }

We don't really need to put cond.wait() in an if statement. It will check the condition before waiting.
I'd probably go for something more like:
void thread_func() {
    while(true) {
        
        std::unique_lock lk{pool_mutex};
        cond.wait(lk, [this]() { return !task_queue.empty() || stopped; });
        
        if (stopped)
            break;

        assert(!task_queue.empty());
        
        auto callable = std::move(task_queue.front());
        task_queue.pop();
        
        lk.unlock();
        
        callable();
    }
}