Concurrent quicksort in C++
Partition - Pivot selection
There are 2 bugs in the pivot selection code, in combination allowing for \$\mathcal{O}(n^2)\$ worst case runtime.
Bug #1: Wrong average calculation
I guess this is just a typo:
auto middle = data[(p + 2) / 2];

Most likely, (p + r) / 2 was intended instead, and that would fix this problem (disregarding overflow).
Bug #2: Missing a case
if (first &gt; middle) {
    if (middle &gt; last) {
        pivot = middle;
    } else {
        pivot = last;
    }
}

This snippet doesn't handle the case correctly if \$middle &lt; first &lt; last\$. Even though first should be assigned as pivot, last will be.
The fix is simple: Change pivot = last; to pivot = std::min(first, last);.
Consequences
These two bugs allow for runtime complexity to degrade to \$\mathcal{O}(n^2)\$, because they introduce a huge bias towards last as pivot value:
For any call with p &gt;= 4, middle will have the value of an element to the left of data[p], thus being guaranteed to be smaller than first or last due to previous runs of _partition. So `last will be chosen as pivot, due to bug #2.
For any call with p == 2 or p == 3, middle will be assigned the value of data[1], which is equal to first. In that case, pivot will always be assigned the value of middle/first.
For any call with p == 0 or p == 1, the median calculation will work with a slight bias towards last (due to bug #2).
Partition - Moving values
There seems to be an assumption that data[r] == pivot. This might have been a good assumption before due to bug #2, but that isn't necessarily the case once that bug is fixed. There a multiple ways to fix that.
Also, technically it isn't necessary to move values around that are equal to the pivot value: It doesn't actually matter whether they are left or right of the final pivot position.
Multithreading
The current multithreading solution isn't actually that efficient at using threads: Nearly half the threads at a time are waiting for the others to finish. (The first thread is waiting on threads #2 and #3 it spawned, they then are waiting on threads #4-7, ...)
As mentioned in the comments, this could be fixed by letting the original thread keep working on one of the subsections before waiting, i.e. changing this:
        std::thread lower(&amp;quicksort::_sort&lt;T&gt;, this, data, p, q - 1);
        std::thread upper(&amp;quicksort::_sort&lt;T&gt;, this, data, q + 1, r);
        lower.join();
        upper.join();

to:
        std::thread lower(&amp;quicksort::_sort&lt;T&gt;, this, data, p, q - 1);
        _sort(data, q + 1, r);
        lower.join();

Another point to make is that it rarely benefits performance to have a number of threads much larger than the number of available (logical) cores. The final number is likely between #cores and 2 * #cores, but to find the best amount you'll likely need to measure.
Bug #3: No protection for concurrent calls
Since we're already in the multithreaded environment, what happens if a second thread calls quicksort::sort after the first thread, but before that call finished?
It resets thread_count, thus allowing for the creation of more threads than intended. Once they are finished, thread_count will be negative! This might cause problems if that case isn't expected.
Solution: Either don't reset thread_count, or wait until it reaches zero before starting to work on the next dataset.