ProducerConsumerQueue::read has below code in the beginning to check if queue is empty, memory_order_relaxed looks bit suspicious. Presume the reason isEmpty is not called here is to avoid load readIndex twice, but isEmpty uses memory_order_consume on both readIndex and writeIndex.
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
// queue is empty
return false;
}