33#ifdef __has_cpp_attribute
34#if __has_cpp_attribute(nodiscard)
35#define RIGTORP_NODISCARD [[nodiscard]]
38#ifndef RIGTORP_NODISCARD
39#define RIGTORP_NODISCARD
44template <
typename T,
typename Allocator = std::allocator<T>>
class SPSCQueue {
46#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
47 template <
typename Alloc2,
typename =
void>
48 struct has_allocate_at_least : std::false_type {};
50 template <
typename Alloc2>
51 struct has_allocate_at_least<
52 Alloc2, std::void_t<typename Alloc2::value_type,
53 decltype(std::declval<Alloc2 &>().allocate_at_least(
54 size_t{}))>> : std::true_type {};
59 const Allocator &allocator = Allocator())
60 : capacity_(
capacity), allocator_(allocator) {
67 if (capacity_ > SIZE_MAX - 2 * kPadding) {
68 capacity_ = SIZE_MAX - 2 * kPadding;
71#if defined(__cpp_if_constexpr) && defined(__cpp_lib_void_t)
72 if constexpr (has_allocate_at_least<Allocator>::value) {
73 auto res = allocator_.allocate_at_least(capacity_ + 2 * kPadding);
75 capacity_ = res.count - 2 * kPadding;
77 slots_ = std::allocator_traits<Allocator>::allocate(
78 allocator_, capacity_ + 2 * kPadding);
81 slots_ = std::allocator_traits<Allocator>::allocate(
82 allocator_, capacity_ + 2 * kPadding);
85 static_assert(
alignof(
SPSCQueue<T>) == kCacheLineSize,
"");
86 static_assert(
sizeof(
SPSCQueue<T>) >= 3 * kCacheLineSize,
"");
87 assert(
reinterpret_cast<char *
>(&readIdx_) -
88 reinterpret_cast<char *
>(&writeIdx_) >=
89 static_cast<std::ptrdiff_t
>(kCacheLineSize));
96 std::allocator_traits<Allocator>::deallocate(allocator_, slots_,
97 capacity_ + 2 * kPadding);
104 template <
typename... Args>
106 std::is_nothrow_constructible<T, Args &&...>::value) {
107 static_assert(std::is_constructible<T, Args &&...>::value,
108 "T must be constructible with Args&&...");
109 auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
110 auto nextWriteIdx = writeIdx + 1;
111 if (nextWriteIdx == capacity_) {
114 while (nextWriteIdx == readIdxCache_) {
115 readIdxCache_ = readIdx_.load(std::memory_order_acquire);
117 new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
118 writeIdx_.store(nextWriteIdx, std::memory_order_release);
121 template <
typename... Args>
123 std::is_nothrow_constructible<T, Args &&...>::value) {
124 static_assert(std::is_constructible<T, Args &&...>::value,
125 "T must be constructible with Args&&...");
126 auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
127 auto nextWriteIdx = writeIdx + 1;
128 if (nextWriteIdx == capacity_) {
131 if (nextWriteIdx == readIdxCache_) {
132 readIdxCache_ = readIdx_.load(std::memory_order_acquire);
133 if (nextWriteIdx == readIdxCache_) {
137 new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
138 writeIdx_.store(nextWriteIdx, std::memory_order_release);
142 void push(
const T &v)
noexcept(std::is_nothrow_copy_constructible<T>::value) {
143 static_assert(std::is_copy_constructible<T>::value,
144 "T must be copy constructible");
148 template <
typename P,
typename =
typename std::enable_if<
149 std::is_constructible<T, P &&>::value>::type>
150 void push(P &&v)
noexcept(std::is_nothrow_constructible<T, P &&>::value) {
155 try_push(
const T &v)
noexcept(std::is_nothrow_copy_constructible<T>::value) {
156 static_assert(std::is_copy_constructible<T>::value,
157 "T must be copy constructible");
161 template <
typename P,
typename =
typename std::enable_if<
162 std::is_constructible<T, P &&>::value>::type>
164 try_push(P &&v)
noexcept(std::is_nothrow_constructible<T, P &&>::value) {
169 auto const readIdx = readIdx_.load(std::memory_order_relaxed);
170 if (readIdx == writeIdxCache_) {
171 writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
172 if (writeIdxCache_ == readIdx) {
176 return &slots_[readIdx + kPadding];
180 static_assert(std::is_nothrow_destructible<T>::value,
181 "T must be nothrow destructible");
182 auto const readIdx = readIdx_.load(std::memory_order_relaxed);
183 assert(writeIdx_.load(std::memory_order_acquire) != readIdx &&
184 "Can only call pop() after front() has returned a non-nullptr");
185 slots_[readIdx + kPadding].~T();
186 auto nextReadIdx = readIdx + 1;
187 if (nextReadIdx == capacity_) {
190 readIdx_.store(nextReadIdx, std::memory_order_release);
194 std::ptrdiff_t diff = writeIdx_.load(std::memory_order_acquire) -
195 readIdx_.load(std::memory_order_acquire);
199 return static_cast<size_t>(diff);
203 return writeIdx_.load(std::memory_order_acquire) ==
204 readIdx_.load(std::memory_order_acquire);
210#ifdef __cpp_lib_hardware_interference_size
211 static constexpr size_t kCacheLineSize =
212 std::hardware_destructive_interference_size;
214 static constexpr size_t kCacheLineSize = 64;
218 static constexpr size_t kPadding = (kCacheLineSize - 1) /
sizeof(T) + 1;
223#if defined(__has_cpp_attribute) && __has_cpp_attribute(no_unique_address)
224 Allocator allocator_ [[no_unique_address]];
226 Allocator allocator_;
232 alignas(kCacheLineSize) std::atomic<size_t> writeIdx_ = {0};
233 alignas(kCacheLineSize)
size_t readIdxCache_ = 0;
234 alignas(kCacheLineSize) std::atomic<size_t> readIdx_ = {0};
235 alignas(kCacheLineSize)
size_t writeIdxCache_ = 0;
#define RIGTORP_NODISCARD
RIGTORP_NODISCARD T * front() noexcept
SPSCQueue(const SPSCQueue &)=delete
SPSCQueue & operator=(const SPSCQueue &)=delete
void emplace(Args &&...args) noexcept(std::is_nothrow_constructible< T, Args &&... >::value)
void push(P &&v) noexcept(std::is_nothrow_constructible< T, P && >::value)
RIGTORP_NODISCARD bool empty() const noexcept
RIGTORP_NODISCARD bool try_push(P &&v) noexcept(std::is_nothrow_constructible< T, P && >::value)
RIGTORP_NODISCARD bool try_emplace(Args &&...args) noexcept(std::is_nothrow_constructible< T, Args &&... >::value)
RIGTORP_NODISCARD size_t capacity() const noexcept
RIGTORP_NODISCARD size_t size() const noexcept
void push(const T &v) noexcept(std::is_nothrow_copy_constructible< T >::value)
SPSCQueue(const size_t capacity, const Allocator &allocator=Allocator())
RIGTORP_NODISCARD bool try_push(const T &v) noexcept(std::is_nothrow_copy_constructible< T >::value)
#define assert(condition)