text: #pragma once
  #include <condition_variable>
  #include <functional>
  #include <future>
  #include <memory>
  #include <mutex>
  #include <queue>
  #include <stdexcept>
  #include <thread>
  #include <vector>
  
  struct ThreadPool
  {
      ThreadPool(size_t);
      ~ThreadPool();
  
      template <class F, class... Args>
      auto enqueue(F&&, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>;
  
  private:
      std::vector<std::thread> workers;
      std::queue<std::function<void()>> tasks;
      std::mutex queue_mutex;
      std::condition_variable condition;
      bool stop;
  };
  
  inline ThreadPool::ThreadPool(size_t threads = std::thread::hardware_concurrency() - 1)
      : stop(false)
  {
      for (size_t i = 0; i < threads; ++i)
      {
          workers.emplace_back(
              [this]
              {
                  for (;;)
                  {
                      std::function<void()> task;
                      {
                          std::unique_lock<std::mutex> lock(this->queue_mutex);
                          this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
                          if (this->stop && this->tasks.empty()) return;
                          task = std::move(this->tasks.front());
                          this->tasks.pop();
                      }
  
                      task();
                  }
              });
      }
  }
  
  inline ThreadPool::~ThreadPool()
  {
      {
          std::unique_lock<std::mutex> lock(queue_mutex);
          stop = true;
      }
  
      condition.notify_all();
  
      for (std::thread& worker : workers)
      {
          worker.join();
      }
  }
  
  template <class F, class... Args>
  auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<std::invoke_result_t<F, Args...>>
  {
      using return_type = std::invoke_result_t<F, Args...>;
  
      auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
  
      std::future<return_type> res = task->get_future();
      {
          std::unique_lock<std::mutex> lock(queue_mutex);
  
          if (stop)
          {
              throw std::runtime_error("enqueue on stopped ThreadPool");
          }
  
          tasks.emplace([task]() { (*task)(); });
      }
  
      condition.notify_one();
  
      return res;
  }
  