Discussion:
My deque-replaement
(too old to reply)
Bonita Montero
2024-09-20 13:18:22 UTC
Permalink
Just a ring-buffer with functional access:

#pragma once
#include <utility>
#include <cassert>
#include <memory>
#include <span>
#include "invoke_on_destruct.h"
#include "nui.h"

#if defined(__llvm__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wparentheses"
#pragma clang diagnostic ignored "-Wunqualified-std-cast-call"
#endif

template<typename T, typename Alloc = std::allocator<T>>
struct ring_deque
{
ring_deque( ptrdiff_t capacity = 0, Alloc const &alloc = Alloc() );
~ring_deque();
ring_deque( ring_deque<T, Alloc> const &other );
ring_deque( ring_deque<T, Alloc> &&other ) noexcept;
ring_deque<T, Alloc> &operator =( ring_deque<T, Alloc> const &other );
ring_deque<T, Alloc> &operator =( ring_deque<T, Alloc> &&other ) noexcept;
template<bool Forward, typename Consumer>
void scan( Consumer consumer )
requires requires( T &value ) { { consumer( value ) }; };
template<bool Forward, typename Consumer>
void scan( Consumer consumer ) const
requires requires( T const &value ) { { consumer( value ) }; };
T &front() noexcept;
T const &front() const noexcept;
T &back() noexcept;
T const &back() const noexcept;
template<bool Forward, typename Consumer>
size_t consume( Consumer consumer )
requires requires( T &value ) { { consumer( value ) } ->
std::same_as<int>; };
template<bool Back, typename ... Args>
T &emplace( Args &&... args )
requires std::is_constructible_v<T, Args ...>;
void pop_back() noexcept;
void pop_front() noexcept;
void pop_n( ptrdiff_t n ) noexcept;
void shrink_to_fit();
bool empty() const noexcept;
size_t size() const noexcept;
size_t capacity() const noexcept;
void clear();
T &operator []( ptrdiff_t i ) noexcept;
T const &operator []( ptrdiff_t i ) const noexcept;
void reserve( ptrdiff_t capacity );
private:
using span_t = std::span<T>;
span_t allocRing( size_t capacity );
void freeRing( span_t sp );
template<bool Forward, typename Consumer>
void scanInternal( Consumer consumer );
template<bool Forward, typename Consumer>
void scanInternal( span_t::iterator &it, Consumer consumer );
void grow();
void reCap( ptrdiff_t capacity );
static size_t normCap( ptrdiff_t capacity );
using alloc_t = Alloc;
NO_UNIQUE_ADDRESS alloc_t m_alloc;
span_t m_ring;
span_t::iterator m_front, m_back;
size_t m_n;
};

template<typename T, typename Alloc>
ring_deque<T, Alloc>::ring_deque( ptrdiff_t capacity, Alloc const &alloc ) :
m_alloc( alloc ),
m_ring( allocRing( normCap( capacity ) ) ),
m_front( m_ring.begin() ),
m_back( m_ring.begin() ),
m_n( 0 )
{
}

template<typename T, typename Alloc>
ring_deque<T, Alloc>::~ring_deque()
{
consume<true>( [&]( T & ) -> int { return 1; } );
freeRing( m_ring );
}

template<typename T, typename Alloc>
ring_deque<T, Alloc>::ring_deque( ring_deque<T, Alloc> const &other ) :
ring_deque( other.m_n, other.m_alloc )
{
invoke_on_destruct reset( [&] { this->~ring_deque(); } );
other.scanInternal<true>( [&]( T const &value ) { emplace_back( value
); return true; } );
reset.disable();
}

template<typename T, typename Alloc>
ring_deque<T, Alloc>::ring_deque( ring_deque<T, Alloc> &&other ) noexcept :
m_alloc( other.m_alloc ),
m_ring( other.m_ring ),
m_front( other.m_front ),
m_back( other.m_back ),
m_n( other.m_n )
{
other.m_ring = span_t();
other.m_n = 0;
#if !defined(NDEBUG)
other.m_front = span_t().begin();
other.m_back = span_t().begin();
#endif
}

template<typename T, typename Alloc>
ring_deque<T, Alloc> &ring_deque<T, Alloc>::operator =( ring_deque<T,
Alloc> const &other )
{
clear();
reserve( other.size() );
other.scanInternal<true>( [&]( T const &value ) { emplace_back( value
); return true; } );
return *this;
}

template<typename T, typename Alloc>
ring_deque<T, Alloc> &ring_deque<T, Alloc>::operator =( ring_deque<T,
Alloc> &&other ) noexcept
{
freeRing( m_ring );
m_alloc = other.m_alloc;
m_ring = other.m_ring;
m_front = other.m_front;
m_back = other.m_back;
m_n = other.m_n;
other.m_ring = span_t();
other.m_n = 0;
#if !defined(NDEBUG)
other.m_front = span_t().begin();
other.m_back = span_t().begin();
#endif
return *this;
}

template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scan( Consumer consumer )
requires requires( T &value ) { { consumer( value ) }; }
{
using namespace std;
scanInternal<Forward>( [&]( T &value )
{
bool ret = true;
if constexpr( requires() { { consumer( value ) } ->
convertible_to<bool>; } )
ret = consumer( value );
else
consumer( value );
return ret;
} );
}

template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scan( Consumer consumer ) const
requires requires( T const &value ) { { consumer( value ) }; }
{
const_cast<ring_deque<T, Alloc> *>(this)->scan<Forward>( [&]( T &value
) { return consumer( value ); } );
}

template<typename T, typename Alloc>
inline T &ring_deque<T, Alloc>::front() noexcept
{
assert(m_n);
return *m_front;
}

template<typename T, typename Alloc>
inline T const &ring_deque<T, Alloc>::front() const noexcept
{
assert(m_n);
return *m_front;
}

template<typename T, typename Alloc>
inline T &ring_deque<T, Alloc>::back() noexcept
{
assert(m_n);
return m_back[-1];
}

template<typename T, typename Alloc>
inline T const &ring_deque<T, Alloc>::back() const noexcept
{
assert(m_n);
return m_back[-1];
}

template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline size_t ring_deque<T, Alloc>::consume( Consumer consumer )
requires requires( T &value ) { { consumer( value ) } ->
std::same_as<int>; }
{
size_t nInitial = m_n;
typename span_t::iterator it = Forward ? m_front : m_back;
invoke_on_destruct adjust( [&] { (Forward ? m_front : m_back) = it; } );
scanInternal<Forward>( it, [&]( T &value ) -> bool
{
int cont = consumer( value );
if( cont >= 0 ) [[likely]]
{
value.~T();
--m_n;
}
return cont > 0;
} );
return nInitial - m_n;
}

template<typename T, typename Alloc>
template<bool Back, typename ... Args>
T &ring_deque<T, Alloc>::emplace( Args &&... args )
requires std::is_constructible_v<T, Args ...>
{
using namespace std;
assert(m_ring.size() >= m_n);
if( m_n == m_ring.size() ) [[unlikely]]
grow();
auto next = Back ? m_back : m_front;
if( next == (Back ? m_ring.end() : m_ring.begin()) ) [[unlikely]]
next = Back ? m_ring.begin() : m_ring.end();
next -= !Back;
new( (void *)&*next ) T( forward<Args>( args ) ... );
next += Back;
(Back ? m_back : m_front) = next;
++m_n;
return *(Back ? m_back - 1 : m_front);
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::pop_back() noexcept
{
assert(m_n);
(*--m_back).~T();
if( m_back == m_ring.begin() ) [[unlikely]]
m_back = m_ring.end();
--m_n;
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::pop_front() noexcept
{
assert(m_n);
(*m_front++).~T();
if( m_front == m_ring.end() ) [[unlikely]]
m_front = m_ring.begin();
--m_n;
}

template<typename T, typename Alloc>
void ring_deque<T, Alloc>::pop_n( ptrdiff_t n ) noexcept
{
if( n > 0 )
{
assert(m_n >= (size_t)n);
size_t dest = m_n - n;
consume<true>( [&]( T &value ) -> int { return --m_n != dest; } );
}
else if( n < 0 )
{
assert(m_n >= (size_t)-n);
size_t dest = m_n - -n;
consume<false>( [&]( T &value ) -> int { return --m_n != dest; } );
}
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::shrink_to_fit()
{
reCap( m_n );
}

template<typename T, typename Alloc>
inline bool ring_deque<T, Alloc>::empty() const noexcept
{
return !size();
}

template<typename T, typename Alloc>
inline size_t ring_deque<T, Alloc>::size() const noexcept
{
return m_n;
}

template<typename T, typename Alloc>
inline size_t ring_deque<T, Alloc>::capacity() const noexcept
{
return m_ring.size();
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::clear()
{
pop_n( m_n );
}

template<typename T, typename Alloc>
T &ring_deque<T, Alloc>::operator []( ptrdiff_t i ) noexcept
{
using namespace std;
if( i >= 0 )
{
assert((size_t)i < m_n);
ptrdiff_t headRun = m_ring.end() - m_front;
if( i < headRun )
return m_front[i];
else
return m_ring.begin()[i - headRun];
}
else
{
assert((size_t)-i <= m_n);
ptrdiff_t tailRun = m_ring.begin() - m_back;
if( i >= tailRun )
return m_back[i];
else
return m_ring.end()[i - tailRun];
}
}

template<typename T, typename Alloc>
inline T const &ring_deque<T, Alloc>::operator []( ptrdiff_t i ) const
noexcept
{
return (*const_cast<ring_deque<T, Alloc> *>(this))[i];
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::reserve( ptrdiff_t capacity )
{
reCap( capacity );
}

template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scanInternal( Consumer consumer )
{
typename span_t::iterator it;
scanInternal<Forward>( it, consumer );
}

template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scanInternal( span_t::iterator &it,
Consumer consumer )
{
using namespace std;
it = Forward ? m_front : m_back;
for( size_t n = m_n; n; --n )
{
if( !consumer( it[-(ptrdiff_t)!Forward] ) ) [[unlikely]]
break;
it = it + (Forward - !Forward);
if( it == (Forward ? m_ring.end() : m_ring.begin()) ) [[unlikely]]
it = Forward ? m_ring.begin() : m_ring.end();
}
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::grow()
{
size_t nBefore = m_ring.size();
reCap( 2 * nBefore + (size_t)!nBefore );
}

template<typename T, typename Alloc>
void ring_deque<T, Alloc>::reCap( ptrdiff_t capacity )
{
using namespace std;
size_t n = normCap( capacity );;
if( n <= m_n )
return;
span_t ring( allocRing( n ) );
size_t nElements = m_n;
invoke_on_destruct finalize( [&]
{
m_n = nElements;
freeRing( ring );
} );
auto it = ring.begin();
invoke_on_destruct moveBack( [&]
{
while( it != ring.begin() )
{
if constexpr( is_move_constructible_v<T> )
emplace<false>( move( *--it ) );
(*it).~T();
}
} );
if constexpr( is_move_constructible_v<T> )
consume<true>( [&]( T &value )
{
new( (void *)&*it ) T( move( value ) );
++it;
return 1;
} );
else
scan<true>( [&]( T &value )
{
new( (void *)&*it ) T( move( value ) );
++it;
return true;
} );
moveBack.disable();
swap( m_ring, ring );
m_front = m_ring.begin();
m_back = it;
}

template<typename T, typename Alloc>
inline size_t ring_deque<T, Alloc>::normCap( ptrdiff_t capacity )
{
if( capacity < 0 ) [[unlikely]]
capacity = (size_t)-capacity / sizeof(T);
return capacity;
}

template<typename T, typename Alloc>
typename ring_deque<T, Alloc>::span_t ring_deque<T, Alloc>::allocRing(
size_t capacity )
{
using namespace std;
if( !capacity )
return span_t();
#if !defined(__cpp_lib_allocate_at_least) && defined(_MSC_VER)
allocation_result<T *, size_t> ar( m_alloc.allocate_at_least( capacity ) );
return span_t( ar.ptr, ar.count );
#else
return span_t( m_alloc.allocate( capacity ), capacity );
#endif
}

template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::freeRing( span_t sp )
{
if( to_address( sp.begin() ) )
m_alloc.deallocate( sp.data(), sp.size() );
}

#if defined(__llvm__)
#pragma clang diagnostic pop
#endif
M***@dastardlyhq.com
2024-09-20 14:10:14 UTC
Permalink
On Fri, 20 Sep 2024 15:18:22 +0200
A ring buffer can be written about 10 lines of code give or take. You seem to
revel in making simple things complicated. One day post us your version of
Hello World so we can have a laugh.
Bonita Montero
2024-09-20 14:18:07 UTC
Permalink
A ring buffer can be written about 10 lines of code ...
A deque is also much more complex than you think it is necessary.
M***@dastardlyhq.com
2024-09-20 15:08:15 UTC
Permalink
On Fri, 20 Sep 2024 16:18:07 +0200
Post by Bonita Montero
A ring buffer can be written about 10 lines of code ...
A deque is also much more complex than you think it is necessary.
A deque is not needed for a ring buffer. Using one demonstrates you don't
actually know what a ring buffer is. Hint: its an array with 2 pointers/indexes.
Done.
Bonita Montero
2024-09-20 15:12:20 UTC
Permalink
Post by M***@dastardlyhq.com
A deque is not needed for a ring buffer.
Both usually serve the same purpose. A deque has iterators and therefore
the source of the MSVC-implementation is about 1.800 LOCs. If I'd write
such an implementation and I would post it here you'd say that all this
isn't necessary.
Post by M***@dastardlyhq.com
Using one demonstrates you don't actually know what a ring buffer is.
You are overburdened with my code and you didn't read it.
I implemented a very convenient to use ring-buffer.
M***@dastardlyhq.com
2024-09-20 15:57:31 UTC
Permalink
On Fri, 20 Sep 2024 17:12:20 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
A deque is not needed for a ring buffer.
Both usually serve the same purpose. A deque has iterators and therefore
No, they don't. A deque can have an unbounded (except by memory) number of
items whereas a ring buffer is by design a fixed size.
Post by Bonita Montero
Post by M***@dastardlyhq.com
Using one demonstrates you don't actually know what a ring buffer is.
You are overburdened with my code and you didn't read it.
I implemented a very convenient to use ring-buffer.
A ring buffer doesn't require 420 lines of code. People like yourself are
responsible for the absurd amount of bloat in modern code and ridiculously
powerful CPUs being required just to run office style applications and web
pages.
Bonita Montero
2024-09-20 16:03:14 UTC
Permalink
Post by M***@dastardlyhq.com
No, they don't. A deque can have an unbounded (except by memory)
number of items whereas a ring buffer is by design a fixed size.
A ring-buffer can be growing, my implementation also does grow.
The Boost ringbuffer also can grow.
Post by M***@dastardlyhq.com
A ring buffer doesn't require 420 lines of code.
The code is rather small since I use a lot of functional programming
callbacks which re-uses existing code - with maximum performance.
For the one who uses my code it's extremely convenient.

ring_deque<string> rds;
...
rds.consume<true>( [&]( string &str )
{
cout << str << endl;
} );
Post by M***@dastardlyhq.com
People like yourself are responsible for the absurd amount of bloat
in modern code and ridiculously powerful CPUs being required just
to run office style applications and web pages.
The "bloat" results in very few code of who uses this "bloat".
You're simply overburdened. Have a look at the standard library
code, it's much more complex for things which might look trivial
to you.
Bonita Montero
2024-09-20 16:09:26 UTC
Permalink
Post by Bonita Montero
A ring-buffer can be growing, my implementation also does grow.
The Boost ringbuffer also can grow.
The current Boost circular_buffer is 272kB, scattered in multiple
files. That's not bloat !
M***@dastardlyhq.com
2024-09-21 09:40:39 UTC
Permalink
On Fri, 20 Sep 2024 18:03:14 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
No, they don't. A deque can have an unbounded (except by memory)
number of items whereas a ring buffer is by design a fixed size.
A ring-buffer can be growing, my implementation also does grow.
A ringbuffer that can grow is not a ring buffer, its a deque as you figured
out.
Post by Bonita Montero
The Boost ringbuffer also can grow.
Boost has a lot to answer for in many areas.
Post by Bonita Montero
Post by M***@dastardlyhq.com
People like yourself are responsible for the absurd amount of bloat
in modern code and ridiculously powerful CPUs being required just
to run office style applications and web pages.
The "bloat" results in very few code of who uses this "bloat".
You're simply overburdened. Have a look at the standard library
Am I? I think it might be you who can't think clearly enough to write a
clean simple algorithm for a simple data structure.
Post by Bonita Montero
code, it's much more complex for things which might look trivial
to you.
Ring buffers are trivial.
Bonita Montero
2024-09-21 10:54:15 UTC
Permalink
Post by M***@dastardlyhq.com
On Fri, 20 Sep 2024 18:03:14 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
No, they don't. A deque can have an unbounded (except by memory)
number of items whereas a ring buffer is by design a fixed size.
A ring-buffer can be growing, my implementation also does grow.
A ringbuffer that can grow is not a ring buffer, its a deque as you figured
out.
It's unspecified whether a ring-buffer can grow. All deque<>-implemen-
tations are ring-buffers since the chunk-list is implemented as a ring.
Post by M***@dastardlyhq.com
Post by Bonita Montero
The Boost ringbuffer also can grow.
Boost has a lot to answer for in many areas.
Boost is for sure too complex for you.
Post by M***@dastardlyhq.com
Post by Bonita Montero
Post by M***@dastardlyhq.com
People like yourself are responsible for the absurd amount of bloat
in modern code and ridiculously powerful CPUs being required just
to run office style applications and web pages.
The "bloat" results in very few code of who uses this "bloat".
You're simply overburdened. Have a look at the standard library
Am I? I think it might be you who can't think clearly enough to write a
clean simple algorithm for a simple data structure.
But it's for sure not that convenient to use.
Post by M***@dastardlyhq.com
Post by Bonita Montero
code, it's much more complex for things which might look trivial
to you.
Ring buffers are trivial.
Not if they're comfortable to use.
Chris Ahlstrom
2024-09-21 12:18:57 UTC
Permalink
Post by M***@dastardlyhq.com
On Fri, 20 Sep 2024 18:03:14 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
No, they don't. A deque can have an unbounded (except by memory)
number of items whereas a ring buffer is by design a fixed size.
A ring-buffer can be growing, my implementation also does grow.
A ringbuffer that can grow is not a ring buffer, its a deque as you figured
out.
Post by Bonita Montero
The Boost ringbuffer also can grow.
Boost has a lot to answer for in many areas.
Post by Bonita Montero
Post by M***@dastardlyhq.com
People like yourself are responsible for the absurd amount of bloat
in modern code and ridiculously powerful CPUs being required just
to run office style applications and web pages.
The "bloat" results in very few code of who uses this "bloat".
You're simply overburdened. Have a look at the standard library
Am I? I think it might be you who can't think clearly enough to write a
clean simple algorithm for a simple data structure.
Post by Bonita Montero
code, it's much more complex for things which might look trivial
to you.
Ring buffers are trivial.
Depends what you want. The ringbuffer.c module in the jack2 project is 420
lines including comments and #ifdefs. It also provides optional locking, and
some convenience functions.

I wrote a ringbuffer template class that is "similar" in size and scope.
Probably could refine it further.

Apropos of nothing:

https://mathworld.wolfram.com/Trivial.html

According to the Nobel Prize-winning physicist Richard Feynman (Feynman
1997), mathematicians designate any theorem as "trivial" once a proof has
been obtained--no matter how difficult the theorem was to prove in the
first place. There are therefore exactly two types of true mathematical
propositions: trivial ones, and those which have not yet been proven.
--
Have at you!
Bonita Montero
2024-09-21 12:25:18 UTC
Permalink
Post by Chris Ahlstrom
I wrote a ringbuffer template class that is "similar" in size and scope.
Probably could refine it further.
He thinks he could write that in 10 lines without noticing the requirements.
M***@dastardlyhq.com
2024-09-21 15:59:57 UTC
Permalink
On Sat, 21 Sep 2024 14:25:18 +0200
Post by Bonita Montero
Post by Chris Ahlstrom
I wrote a ringbuffer template class that is "similar" in size and scope.
Probably could refine it further.
He thinks he could write that in 10 lines without noticing the requirements.
I implented the sound output buffer for a virtual synth as a ring buffer.
As you can imagine time constraints were on the order of milliseconds as the
buffer itself only contained 1/20th of a second of PCM data.

Normally the read and write pointers kept more or less in lock step depending
on how the threads were running but not always, and you absolutely cannot have
the sound stop so if the read pointer had to go around again so be it, you
might hear a small click but usually not.

It didn't require 420 lines to implement because if it had it would have been
so slow the sound would have been glitching all over the place.

Unlike you sitting in your ivory academic tower I have real world experience
of these things.
Bonita Montero
2024-09-21 16:12:21 UTC
Permalink
Post by M***@dastardlyhq.com
I implented the sound output buffer for a virtual synth as a ring buffer.
That's a ring for _trivial_ C++ types, that's much easier. I can handle
complex C++ types with a vector<>-like emplace( )and I move them when
the ring is resized; that's much more effort.
Post by M***@dastardlyhq.com
It didn't require 420 lines to implement because if it had it would have been
so slow the sound would have been glitching all over the place.
Your requirements are simply totally different since you don't need to
handle resizes and complex types.
Post by M***@dastardlyhq.com
Unlike you sitting in your ivory academic tower I have real world experience
of these things.
I wrote my little class for a real purpose.

And you need a dozen postings to understand what the static and variable
initialization parameters for a semaphore are good for.
M***@dastardlyhq.com
2024-09-22 07:58:03 UTC
Permalink
On Sat, 21 Sep 2024 18:12:21 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
I implented the sound output buffer for a virtual synth as a ring buffer.
That's a ring for _trivial_ C++ types, that's much easier. I can handle
Watch those goalposts move.
Post by Bonita Montero
complex C++ types with a vector<>-like emplace( )and I move them when
the ring is resized; that's much more effort.
For complex types any sane person would just use an STL container as the
basis of a ring buffer instead of reinventing the wheel like you've done. And
probably made it square in the process.
Post by Bonita Montero
And you need a dozen postings to understand what the static and variable
initialization parameters for a semaphore are good for.
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Bonita Montero
2024-09-22 08:48:07 UTC
Permalink
Post by M***@dastardlyhq.com
For complex types any sane person would just use an STL container as the
basis of a ring buffer instead of reinventing the wheel like you've done.
And the STL-container is a magnitude more complex. I developed
this little alternative because I needed a qeue for MSVC which
is as efficient as libstdc++'s or libc++'s deque.
Post by M***@dastardlyhq.com
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
My code simply works and it it maximally efficient and convenient to use.
M***@dastardlyhq.com
2024-09-22 09:01:46 UTC
Permalink
On Sun, 22 Sep 2024 10:48:07 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
For complex types any sane person would just use an STL container as the
basis of a ring buffer instead of reinventing the wheel like you've done.
And the STL-container is a magnitude more complex. I developed
So what? The containers are already written for you and many clever eyes and
minds have made sure they're as efficient as possible.
Post by Bonita Montero
this little alternative because I needed a qeue for MSVC which
is as efficient as libstdc++'s or libc++'s deque.
So why didn't you just use std::deque then with a couple of wrapper functions.
Bonita Montero
2024-09-22 09:49:45 UTC
Permalink
Post by M***@dastardlyhq.com
So what? The containers are already written for you and many clever eyes and
minds have made sure they're as efficient as possible.
You're not just trying to explain away complexity with me because
it's too much for you.
Post by M***@dastardlyhq.com
So why didn't you just use std::deque then with a couple of wrapper functions.
Because MSVC's std::deque and Boost's boost::container::deque is
multiple times slower. I needed a fast solution for all platforms.
M***@dastardlyhq.com
2024-09-22 15:13:22 UTC
Permalink
On Sun, 22 Sep 2024 11:49:45 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
So what? The containers are already written for you and many clever eyes and
minds have made sure they're as efficient as possible.
You're not just trying to explain away complexity with me because
it's too much for you.
If you say so poppet.
Post by Bonita Montero
Post by M***@dastardlyhq.com
So why didn't you just use std::deque then with a couple of wrapper
functions.
Because MSVC's std::deque and Boost's boost::container::deque is
multiple times slower. I needed a fast solution for all platforms.
Then use a vector.

No doubt you'd write own crypto libraries too given half a chance.
Bonita Montero
2024-09-22 15:28:28 UTC
Permalink
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 11:49:45 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
So what? The containers are already written for you and many clever eyes and
minds have made sure they're as efficient as possible.
You're not just trying to explain away complexity with me because
it's too much for you.
If you say so poppet.
You deny a lot of advanced technologies.
Post by M***@dastardlyhq.com
Then use a vector.
Then I'd to manage the whole ring-issues. So I wrote the code once
and now I have a maximally efficient solution which is only 400 LOCs.
Post by M***@dastardlyhq.com
No doubt you'd write own crypto libraries too given half a chance.
If I had no alternative like in this case ... But that's unlikely.
Chris Ahlstrom
2024-09-22 10:49:13 UTC
Permalink
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
--
When one burns one's bridges, what a very nice fire it makes.
-- Dylan Thomas
Bonita Montero
2024-09-22 11:00:38 UTC
Permalink
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
Muttley denies every language feature he doesn't understand in 10s.
M***@dastardlyhq.com
2024-09-22 15:19:48 UTC
Permalink
On Sun, 22 Sep 2024 13:00:38 +0200
Post by Bonita Montero
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
Muttley denies every language feature he doesn't understand in 10s.
Wtf is "in 10s" supposed to mean?

As for not understanding - its a case of law of diminishing returns. No one
can learn the whole of C++ anymore unlike C which means any one bit of code
might present challenges to even an experienced dev at maintenance time
which often means poorer quality software.

Unfortuntely C++ is now a dogs dinner. I use it because I know it but I
certainly wouldn't choose it if I didn't.
Bonita Montero
2024-09-22 15:30:57 UTC
Permalink
Post by M***@dastardlyhq.com
As for not understanding - its a case of law of diminishing returns. No one
can learn the whole of C++ anymore unlike C which means any one bit of code
might present challenges to even an experienced dev at maintenance time
which often means poorer quality software.
I can handle 90% of the language and I would have no problems learning
the remaining 10% if necessary. I'm constantly updating my knowledge
by buying books on Leanpu which update me for newer standards.
Post by M***@dastardlyhq.com
Unfortuntely C++ is now a dogs dinner. I use it because I know it but I
certainly wouldn't choose it if I didn't.
You have the choice of writing all details yourself like in C or to use
a more powerful language. Then you'd need to learn advanced technologies
once and after some time you save a lot of time and work.
M***@dastardlyhq.com
2024-09-22 15:16:37 UTC
Permalink
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
Bonita Montero
2024-09-22 15:31:34 UTC
Permalink
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
You said you could replace my code with 10 lines of code - without
noticing what it does.
M***@dastardlyhq.com
2024-09-23 07:35:26 UTC
Permalink
On Sun, 22 Sep 2024 17:31:34 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
You said you could replace my code with 10 lines of code - without
noticing what it does.
I was refering to how someone explained the reason for the template param of
semaphores in 2 lines whereas you and others spent paragraphs trying and
failing.
Bonita Montero
2024-09-23 07:36:32 UTC
Permalink
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 17:31:34 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2 lines.
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
You said you could replace my code with 10 lines of code - without
noticing what it does.
I was refering to how someone explained the reason for the template param of
semaphores in 2 lines whereas you and others spent paragraphs trying and
failing.
You needed several descriptions which were all correct.
M***@dastardlyhq.com
2024-09-23 09:31:09 UTC
Permalink
On Mon, 23 Sep 2024 09:36:32 +0200
Post by M***@dastardlyhq.com
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 17:31:34 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2
lines.
Post by M***@dastardlyhq.com
Post by Bonita Montero
Post by M***@dastardlyhq.com
Post by Chris Ahlstrom
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
You said you could replace my code with 10 lines of code - without
noticing what it does.
I was refering to how someone explained the reason for the template param of
semaphores in 2 lines whereas you and others spent paragraphs trying and
failing.
You needed several descriptions which were all correct.
Several descriptions which all parotted the cppreference page which doesn't
explain it properly otherwise I wouldn't have asked.
Bonita Montero
2024-09-23 09:47:41 UTC
Permalink
Post by M***@dastardlyhq.com
Several descriptions which all parotted the cppreference page which doesn't
explain it properly otherwise I wouldn't have asked.
https://en.cppreference.com/w/cpp/thread/counting_semaphore
... is correct.
Michael S
2024-09-23 10:42:12 UTC
Permalink
On Mon, 23 Sep 2024 09:31:09 -0000 (UTC)
Post by M***@dastardlyhq.com
On Mon, 23 Sep 2024 09:36:32 +0200
Post by M***@dastardlyhq.com
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 17:31:34 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2
lines.
Post by M***@dastardlyhq.com
Post by Bonita Montero
Post by M***@dastardlyhq.com
Post by Chris Ahlstrom
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
You said you could replace my code with 10 lines of code - without
noticing what it does.
I was refering to how someone explained the reason for the
template param of semaphores in 2 lines whereas you and others
spent paragraphs trying and failing.
You needed several descriptions which were all correct.
Several descriptions which all parotted the cppreference page which
doesn't explain it properly otherwise I wouldn't have asked.
I wonder which of explanations that you had gotten do you consider
satisfactory?

If you are thinking about explanation given by David Brown then I am
quite sure that despite sounding convincing his explanation is wrong.
In a typical implementation counting semaphores *do not* maintain list
of IDs of waiting threads within std::counting_semaphore object.

The article on cppreference.com that does not try to explain why
LeastMaxValue is a template parameter seems to me as the most adequate,
because logical explanation for that choice does not exist.
It would have been much more logical if the type of the counter rather
than its max value had been chosen as a template parameter. Of course
with requirement that the type has to be signed integer.
David Brown
2024-09-23 12:09:51 UTC
Permalink
Post by Michael S
On Mon, 23 Sep 2024 09:31:09 -0000 (UTC)
Post by M***@dastardlyhq.com
On Mon, 23 Sep 2024 09:36:32 +0200
Post by M***@dastardlyhq.com
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 17:31:34 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
On Sun, 22 Sep 2024 06:49:13 -0400
Post by Chris Ahlstrom
<snip>
And you required a number of posts using loads of gibberish to attempt to
explain yet you still failed. Meanwhile someone else managed it in 2
lines.
Post by M***@dastardlyhq.com
Post by Bonita Montero
Post by M***@dastardlyhq.com
Post by Chris Ahlstrom
Ahhh, the famous "2 lines of code" claim.
Who said anything about code?
You said you could replace my code with 10 lines of code - without
noticing what it does.
I was refering to how someone explained the reason for the
template param of semaphores in 2 lines whereas you and others
spent paragraphs trying and failing.
You needed several descriptions which were all correct.
Several descriptions which all parotted the cppreference page which
doesn't explain it properly otherwise I wouldn't have asked.
I wonder which of explanations that you had gotten do you consider
satisfactory?
If you are thinking about explanation given by David Brown then I am
quite sure that despite sounding convincing his explanation is wrong.
In a typical implementation counting semaphores *do not* maintain list
of IDs of waiting threads within std::counting_semaphore object.
I am not at all sure how counting semaphores might be implemented on
different OS's, and I did not claim that they actually held such lists.
I was merely pointing out plausible reasons for why a template class
like this might have an integer template parameter, and how it could be
used for more efficient implementations depending on the maximum count.
Perhaps a more realistic case would be an OS which provided a hybrid
mutex / binary semaphore object and implements a counting semaphore on
top of that - then a std::counting_semaphore<1> could be significantly
more efficient than one with a maximum count greater than 1.

So please don't read more into my comments than I wrote.
Post by Michael S
The article on cppreference.com that does not try to explain why
LeastMaxValue is a template parameter seems to me as the most adequate,
because logical explanation for that choice does not exist.
It would have been much more logical if the type of the counter rather
than its max value had been chosen as a template parameter. Of course
with requirement that the type has to be signed integer.
Having a LeastMaxValue parameter fits better with usage and offers more
flexibility to the implementation, and is therefore a better choice than
specifying an underlying type for the counter.

The user of a counting semaphore is not interested in details of the
type used for the counter - but they are, or should be, interested in a
maximum value for the number of times the counter can be raised. (It
would be nice if the default value were specified to be the maximum
efficiently supported.)

Semaphores are not wrappers around an integer type, so it does not make
sense to define them that way.
Bonita Montero
2024-09-23 17:00:12 UTC
Permalink
Post by David Brown
I am not at all sure how counting semaphores might be implemented on
different OS's, and I did not claim that they actually held such lists.
I was merely pointing out plausible reasons for why a template class
like this might have an integer template parameter, and how it could be
used for more efficient implementations depending on the maximum count.
Perhaps a more realistic case would be an OS which provided a hybrid
mutex / binary semaphore object and implements a counting semaphore on
top of that - then a std::counting_semaphore<1> could be significantly
more efficient than one with a maximum count greater than 1.
I think because C++20 requires futexe (notify() / wait() on atomics)
no implementer misses to use futexe for semaphores. In the following
code, g++, clang++ and MSVC initialize the semaphore with a single
move.

#include <optional>
#include <semaphore>

using namespace std;

optional<counting_semaphore<123>> optSem;

void fn()
{
optSem.emplace( 0 );
}

jseigh
2024-09-21 17:03:47 UTC
Permalink
Post by Chris Ahlstrom
Post by M***@dastardlyhq.com
On Fri, 20 Sep 2024 18:03:14 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
No, they don't. A deque can have an unbounded (except by memory)
number of items whereas a ring buffer is by design a fixed size.
A ring-buffer can be growing, my implementation also does grow.
A ringbuffer that can grow is not a ring buffer, its a deque as you figured
out.
Post by Bonita Montero
The Boost ringbuffer also can grow.
Boost has a lot to answer for in many areas.
Post by Bonita Montero
Post by M***@dastardlyhq.com
People like yourself are responsible for the absurd amount of bloat
in modern code and ridiculously powerful CPUs being required just
to run office style applications and web pages.
The "bloat" results in very few code of who uses this "bloat".
You're simply overburdened. Have a look at the standard library
Am I? I think it might be you who can't think clearly enough to write a
clean simple algorithm for a simple data structure.
Post by Bonita Montero
code, it's much more complex for things which might look trivial
to you.
Ring buffers are trivial.
Depends what you want. The ringbuffer.c module in the jack2 project is 420
lines including comments and #ifdefs. It also provides optional locking, and
some convenience functions.
I wrote a ringbuffer template class that is "similar" in size and scope.
Probably could refine it further.
I have a ringbuffer implementation that is lock-free, internally ABA
proof, and is about 400 loc I think. It supports mpmc, spsc, spmc,
and mpsc.

Joe Seigh
Bonita Montero
2024-09-21 17:13:41 UTC
Permalink
Post by jseigh
I have a ringbuffer implementation that is lock-free, internally ABA
proof, and is about 400 loc I think.  It supports mpmc, spsc, spmc,
and mpsc.
Lock-free ring-buffers are technically nice but suck since they have
to be polled. Better a mutex'd solution with a mutex that has a limited
spin-count and a futex for the slow path.
jseigh
2024-09-21 19:29:27 UTC
Permalink
Post by Bonita Montero
Post by jseigh
I have a ringbuffer implementation that is lock-free, internally ABA
proof, and is about 400 loc I think.  It supports mpmc, spsc, spmc,
and mpsc.
Lock-free ring-buffers are technically nice but suck since they have
to be polled. Better a mutex'd solution with a mutex that has a limited
spin-count and a futex for the slow path.
Lock-free is desirable for certain situations. Also there is a certain
presumption that if you are using lock-free mutable data structures you
sort of know what you are doing. Not rocket science level stuff.
Closer to "don't use uint8_t if the values might exceed 256" or "don't
do unbounded recursion" or "don't use evil regex's".

Joe Seigh
Bonita Montero
2024-09-22 06:23:36 UTC
Permalink
Lock-free is desirable for certain situations.  Also there is a certain
presumption that if you are using lock-free mutable data structures you
sort of know what you are doing.  Not rocket science level stuff.
Closer to "don't use uint8_t if the values might exceed 256" or "don't
do unbounded recursion" or "don't use evil regex's".
Show ma en open source project that uses lock-free queues without a
slow path.
jseigh
2024-09-22 12:10:58 UTC
Permalink
Post by Bonita Montero
Lock-free is desirable for certain situations.  Also there is a certain
presumption that if you are using lock-free mutable data structures you
sort of know what you are doing.  Not rocket science level stuff.
Closer to "don't use uint8_t if the values might exceed 256" or "don't
do unbounded recursion" or "don't use evil regex's".
Show ma en open source project that uses lock-free queues without a
slow path.
Not sure what you mean there. The advantage of a lock-free queue over
a lock based one is that if in the middle of an enqueue/dequeue
operation a thread preempts, all the other threads don't block
waiting for that thread to get dispatched again, complete the
enqueue/dequeue operation and release the lock.

As far as slow path, if you mean waiting for queue to be not empty or
not full, there are eventcounts which can be used to wait for those
conditions w/o busy polling and still keep everything lock-free.
Check out folly's eventcount for more information.

Joe Seigh
Bonita Montero
2024-09-22 13:03:14 UTC
Permalink
Not sure what you mean there.  The advantage of a lock-free queue
over a lock based one is that if in the middle of an enqueue/dequeue
operation a thread preempts, all the other threads don't block
waiting for that thread to get dispatched again, complete the
enqueue/dequeue operation and release the lock.
But the producer could spin if the queue is full or the consumer
could spin if the queue is empty. This is usually inacceptable
because it's inefficient.
Chris M. Thomasson
2024-09-22 18:37:19 UTC
Permalink
Post by Bonita Montero
Not sure what you mean there.  The advantage of a lock-free queue
over a lock based one is that if in the middle of an enqueue/dequeue
operation a thread preempts, all the other threads don't block
waiting for that thread to get dispatched again, complete the
enqueue/dequeue operation and release the lock.
But the producer could spin if the queue is full or the consumer
could spin if the queue is empty. This is usually inacceptable
because it's inefficient.
Are you familiar with eventcounts? The eventcount can be used to wait on
those conditions, well, only if a thread wants to...
Chris M. Thomasson
2024-09-21 20:59:15 UTC
Permalink
Post by jseigh
Post by Chris Ahlstrom
Post by M***@dastardlyhq.com
On Fri, 20 Sep 2024 18:03:14 +0200
Post by Bonita Montero
Post by M***@dastardlyhq.com
No, they don't. A deque can have an unbounded (except by memory)
number of items whereas a ring buffer is by design a fixed size.
A ring-buffer can be growing, my implementation also does grow.
A ringbuffer that can grow is not a ring buffer, its a deque as you figured
out.
Post by Bonita Montero
The Boost ringbuffer also can grow.
Boost has a lot to answer for in many areas.
Post by Bonita Montero
Post by M***@dastardlyhq.com
People like yourself are responsible for the absurd amount of bloat
in modern code and ridiculously powerful CPUs being required just
to run office style applications and web pages.
The "bloat" results in very few code of who uses this "bloat".
You're simply overburdened. Have a look at the standard library
Am I? I think it might be you who can't think clearly enough to write a
clean simple algorithm for a simple data structure.
Post by Bonita Montero
code, it's much more complex for things which might look trivial
to you.
Ring buffers are trivial.
Depends what you want. The ringbuffer.c module in the jack2 project is 420
lines including comments and #ifdefs. It also provides optional locking, and
some convenience functions.
I wrote a ringbuffer template class that is "similar" in size and scope.
Probably could refine it further.
I have a ringbuffer implementation that is lock-free, internally ABA
proof, and is about 400 loc I think.  It supports mpmc, spsc, spmc,
and mpsc.
Nice! Well, supporting all of those modes all in one is interesting to
me Joe. Usually, to gain performance on the various queue types mpmc,
spsc, ect... They each have their own special algorithms. An algorithm
for a spsc is going to be different that a mpmc? Of course mpmc handles
all of those conditions, however, the per impl aspect is important?
jseigh
2024-09-21 22:31:50 UTC
Permalink
Post by Chris M. Thomasson
Post by jseigh
I have a ringbuffer implementation that is lock-free, internally ABA
proof, and is about 400 loc I think.  It supports mpmc, spsc, spmc,
and mpsc.
Nice! Well, supporting all of those modes all in one is interesting to
me Joe. Usually, to gain performance on the various queue types mpmc,
spsc, ect... They each have their own special algorithms. An algorithm
for a spsc is going to be different that a mpmc? Of course mpmc handles
all of those conditions, however, the per impl aspect is important?
The sp and sc code paths use simple stores rather than cas so there is
some speed up from that. Also there is a speed up from not ping ponging
cache so much. You see that speed up in mpmc with single producer
and/or consumer for the same reason though not as much.

Joe Seigh
wij
2024-09-21 23:58:46 UTC
Permalink
Post by Bonita Montero
#pragma once
#include <utility>
#include <cassert>
#include <memory>
#include <span>
#include "invoke_on_destruct.h"
#include "nui.h"
#if defined(__llvm__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wparentheses"
#pragma clang diagnostic ignored "-Wunqualified-std-cast-call"
#endif
template<typename T, typename Alloc = std::allocator<T>>
struct ring_deque
{
ring_deque( ptrdiff_t capacity = 0, Alloc const &alloc = Alloc() );
~ring_deque();
ring_deque( ring_deque<T, Alloc> const &other );
ring_deque( ring_deque<T, Alloc> &&other ) noexcept;
ring_deque<T, Alloc> &operator =( ring_deque<T, Alloc> const &other );
ring_deque<T, Alloc> &operator =( ring_deque<T, Alloc> &&other ) noexcept;
template<bool Forward, typename Consumer>
void scan( Consumer consumer )
requires requires( T &value ) { { consumer( value ) }; };
template<bool Forward, typename Consumer>
void scan( Consumer consumer ) const
requires requires( T const &value ) { { consumer( value ) }; };
T &front() noexcept;
T const &front() const noexcept;
T &back() noexcept;
T const &back() const noexcept;
template<bool Forward, typename Consumer>
size_t consume( Consumer consumer )
requires requires( T &value ) { { consumer( value ) } ->
std::same_as<int>; };
template<bool Back, typename ... Args>
T &emplace( Args &&... args )
requires std::is_constructible_v<T, Args ...>;
void pop_back() noexcept;
void pop_front() noexcept;
void pop_n( ptrdiff_t n ) noexcept;
void shrink_to_fit();
bool empty() const noexcept;
size_t size() const noexcept;
size_t capacity() const noexcept;
void clear();
T &operator []( ptrdiff_t i ) noexcept;
T const &operator []( ptrdiff_t i ) const noexcept;
void reserve( ptrdiff_t capacity );
using span_t = std::span<T>;
span_t allocRing( size_t capacity );
void freeRing( span_t sp );
template<bool Forward, typename Consumer>
void scanInternal( Consumer consumer );
template<bool Forward, typename Consumer>
void scanInternal( span_t::iterator &it, Consumer consumer );
void grow();
void reCap( ptrdiff_t capacity );
static size_t normCap( ptrdiff_t capacity );
using alloc_t = Alloc;
NO_UNIQUE_ADDRESS alloc_t m_alloc;
span_t m_ring;
span_t::iterator m_front, m_back;
size_t m_n;
};
template<typename T, typename Alloc>
m_alloc( alloc ),
m_ring( allocRing( normCap( capacity ) ) ),
m_front( m_ring.begin() ),
m_back( m_ring.begin() ),
m_n( 0 )
{
}
template<typename T, typename Alloc>
ring_deque<T, Alloc>::~ring_deque()
{
consume<true>( [&]( T & ) -> int { return 1; } );
freeRing( m_ring );
}
template<typename T, typename Alloc>
ring_deque( other.m_n, other.m_alloc )
{
invoke_on_destruct reset( [&] { this->~ring_deque(); } );
other.scanInternal<true>( [&]( T const &value ) { emplace_back( value
); return true; } );
reset.disable();
}
template<typename T, typename Alloc>
m_alloc( other.m_alloc ),
m_ring( other.m_ring ),
m_front( other.m_front ),
m_back( other.m_back ),
m_n( other.m_n )
{
other.m_ring = span_t();
other.m_n = 0;
#if !defined(NDEBUG)
other.m_front = span_t().begin();
other.m_back = span_t().begin();
#endif
}
template<typename T, typename Alloc>
ring_deque<T, Alloc> &ring_deque<T, Alloc>::operator =( ring_deque<T,
Alloc> const &other )
{
clear();
reserve( other.size() );
other.scanInternal<true>( [&]( T const &value ) { emplace_back( value
); return true; } );
return *this;
}
template<typename T, typename Alloc>
ring_deque<T, Alloc> &ring_deque<T, Alloc>::operator =( ring_deque<T,
Alloc> &&other ) noexcept
{
freeRing( m_ring );
m_alloc = other.m_alloc;
m_ring = other.m_ring;
m_front = other.m_front;
m_back = other.m_back;
m_n = other.m_n;
other.m_ring = span_t();
other.m_n = 0;
#if !defined(NDEBUG)
other.m_front = span_t().begin();
other.m_back = span_t().begin();
#endif
return *this;
}
template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scan( Consumer consumer )
requires requires( T &value ) { { consumer( value ) }; }
{
using namespace std;
scanInternal<Forward>( [&]( T &value )
{
bool ret = true;
if constexpr( requires() { { consumer( value ) } ->
convertible_to<bool>; } )
ret = consumer( value );
else
consumer( value );
return ret;
} );
}
template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scan( Consumer consumer ) const
requires requires( T const &value ) { { consumer( value ) }; }
{
const_cast<ring_deque<T, Alloc> *>(this)->scan<Forward>( [&]( T &value
) { return consumer( value ); } );
}
template<typename T, typename Alloc>
inline T &ring_deque<T, Alloc>::front() noexcept
{
assert(m_n);
return *m_front;
}
template<typename T, typename Alloc>
inline T const &ring_deque<T, Alloc>::front() const noexcept
{
assert(m_n);
return *m_front;
}
template<typename T, typename Alloc>
inline T &ring_deque<T, Alloc>::back() noexcept
{
assert(m_n);
return m_back[-1];
}
template<typename T, typename Alloc>
inline T const &ring_deque<T, Alloc>::back() const noexcept
{
assert(m_n);
return m_back[-1];
}
template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline size_t ring_deque<T, Alloc>::consume( Consumer consumer )
requires requires( T &value ) { { consumer( value ) } ->
std::same_as<int>; }
{
size_t nInitial = m_n;
typename span_t::iterator it = Forward ? m_front : m_back;
invoke_on_destruct adjust( [&] { (Forward ? m_front : m_back) = it; } );
scanInternal<Forward>( it, [&]( T &value ) -> bool
{
int cont = consumer( value );
if( cont >= 0 ) [[likely]]
{
value.~T();
--m_n;
}
return cont > 0;
} );
return nInitial - m_n;
}
template<typename T, typename Alloc>
template<bool Back, typename ... Args>
T &ring_deque<T, Alloc>::emplace( Args &&... args )
requires std::is_constructible_v<T, Args ...>
{
using namespace std;
assert(m_ring.size() >= m_n);
if( m_n == m_ring.size() ) [[unlikely]]
grow();
auto next = Back ? m_back : m_front;
if( next == (Back ? m_ring.end() : m_ring.begin()) ) [[unlikely]]
next = Back ? m_ring.begin() : m_ring.end();
next -= !Back;
new( (void *)&*next ) T( forward<Args>( args ) ... );
next += Back;
(Back ? m_back : m_front) = next;
++m_n;
return *(Back ? m_back - 1 : m_front);
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::pop_back() noexcept
{
assert(m_n);
(*--m_back).~T();
if( m_back == m_ring.begin() ) [[unlikely]]
m_back = m_ring.end();
--m_n;
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::pop_front() noexcept
{
assert(m_n);
(*m_front++).~T();
if( m_front == m_ring.end() ) [[unlikely]]
m_front = m_ring.begin();
--m_n;
}
template<typename T, typename Alloc>
void ring_deque<T, Alloc>::pop_n( ptrdiff_t n ) noexcept
{
if( n > 0 )
{
assert(m_n >= (size_t)n);
size_t dest = m_n - n;
consume<true>( [&]( T &value ) -> int { return --m_n != dest; } );
}
else if( n < 0 )
{
assert(m_n >= (size_t)-n);
size_t dest = m_n - -n;
consume<false>( [&]( T &value ) -> int { return --m_n != dest; } );
}
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::shrink_to_fit()
{
reCap( m_n );
}
template<typename T, typename Alloc>
inline bool ring_deque<T, Alloc>::empty() const noexcept
{
return !size();
}
template<typename T, typename Alloc>
inline size_t ring_deque<T, Alloc>::size() const noexcept
{
return m_n;
}
template<typename T, typename Alloc>
inline size_t ring_deque<T, Alloc>::capacity() const noexcept
{
return m_ring.size();
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::clear()
{
pop_n( m_n );
}
template<typename T, typename Alloc>
T &ring_deque<T, Alloc>::operator []( ptrdiff_t i ) noexcept
{
using namespace std;
if( i >= 0 )
{
assert((size_t)i < m_n);
ptrdiff_t headRun = m_ring.end() - m_front;
if( i < headRun )
return m_front[i];
else
return m_ring.begin()[i - headRun];
}
else
{
assert((size_t)-i <= m_n);
ptrdiff_t tailRun = m_ring.begin() - m_back;
if( i >= tailRun )
return m_back[i];
else
return m_ring.end()[i - tailRun];
}
}
template<typename T, typename Alloc>
inline T const &ring_deque<T, Alloc>::operator []( ptrdiff_t i ) const
noexcept
{
return (*const_cast<ring_deque<T, Alloc> *>(this))[i];
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::reserve( ptrdiff_t capacity )
{
reCap( capacity );
}
template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scanInternal( Consumer consumer )
{
typename span_t::iterator it;
scanInternal<Forward>( it, consumer );
}
template<typename T, typename Alloc>
template<bool Forward, typename Consumer>
inline void ring_deque<T, Alloc>::scanInternal( span_t::iterator &it,
Consumer consumer )
{
using namespace std;
it = Forward ? m_front : m_back;
for( size_t n = m_n; n; --n )
{
if( !consumer( it[-(ptrdiff_t)!Forward] ) ) [[unlikely]]
break;
it = it + (Forward - !Forward);
if( it == (Forward ? m_ring.end() : m_ring.begin()) ) [[unlikely]]
it = Forward ? m_ring.begin() : m_ring.end();
}
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::grow()
{
size_t nBefore = m_ring.size();
reCap( 2 * nBefore + (size_t)!nBefore );
}
template<typename T, typename Alloc>
void ring_deque<T, Alloc>::reCap( ptrdiff_t capacity )
{
using namespace std;
size_t n = normCap( capacity );;
if( n <= m_n )
return;
span_t ring( allocRing( n ) );
size_t nElements = m_n;
invoke_on_destruct finalize( [&]
{
m_n = nElements;
freeRing( ring );
} );
auto it = ring.begin();
invoke_on_destruct moveBack( [&]
{
while( it != ring.begin() )
{
if constexpr( is_move_constructible_v<T> )
emplace<false>( move( *--it ) );
(*it).~T();
}
} );
if constexpr( is_move_constructible_v<T> )
consume<true>( [&]( T &value )
{
new( (void *)&*it ) T( move( value ) );
++it;
return 1;
} );
else
scan<true>( [&]( T &value )
{
new( (void *)&*it ) T( move( value ) );
++it;
return true;
} );
moveBack.disable();
swap( m_ring, ring );
m_front = m_ring.begin();
m_back = it;
}
template<typename T, typename Alloc>
inline size_t ring_deque<T, Alloc>::normCap( ptrdiff_t capacity )
{
if( capacity < 0 ) [[unlikely]]
capacity = (size_t)-capacity / sizeof(T);
return capacity;
}
template<typename T, typename Alloc>
typename ring_deque<T, Alloc>::span_t ring_deque<T, Alloc>::allocRing(
size_t capacity )
{
using namespace std;
if( !capacity )
return span_t();
#if !defined(__cpp_lib_allocate_at_least) && defined(_MSC_VER)
allocation_result<T *, size_t> ar( m_alloc.allocate_at_least( capacity ) );
return span_t( ar.ptr, ar.count );
#else
return span_t( m_alloc.allocate( capacity ), capacity );
#endif
}
template<typename T, typename Alloc>
inline void ring_deque<T, Alloc>::freeRing( span_t sp )
{
if( to_address( sp.begin() ) )
m_alloc.deallocate( sp.data(), sp.size() );
}
#if defined(__llvm__)
#pragma clang diagnostic pop
#endif
Your ring buffer seems different from https://loosechainsaw.github.io/c++/2020/02/19/ringbuffer/

libwy does not contain ring-buffer, because it is considered much more application-oriented,
i.e. too many incompatible variations, and many are simple.
Bonita Montero
2024-09-22 07:51:23 UTC
Permalink
Post by wij
Your ring buffer seems different from https://loosechainsaw.github.io/c++/2020/02/19/ringbuffer/
This article shows only the class-definition and no methods. The methods
easily could be 1,000 LOCs or more since this ringbuffer supports ite-
rators. For me this was too much work so that I encapsulated common
access-patterns to the ring with functional callbacks (as you can see
from the reply to ***@...
I tested Boost's boost::container::deque yesterday because I hoped it
would be as fast as libstdc++'s or libc++'s deque because MSVC's deque
is a slow; unfortunately the Boost deque was as slow as MSVC's code.
So I stick with my simple implementation which is slightly faster than
libstdc++'s and libc++'s code, but with much less functionality.
wij
2024-09-23 01:04:09 UTC
Permalink
Post by Bonita Montero
Post by wij
Your ring buffer seems different from https://loosechainsaw.github.io/c++/2020/02/19/ringbuffer/
This article shows only the class-definition and no methods. The methods
easily could be 1,000 LOCs or more since this ringbuffer supports ite-
rators. For me this was too much work so that I encapsulated common
access-patterns to the ring with functional callbacks (as you can see
I tested Boost's boost::container::deque yesterday because I hoped it
would be as fast as libstdc++'s or libc++'s deque because MSVC's deque
is a slow; unfortunately the Boost deque was as slow as MSVC's code.
So I stick with my simple implementation which is slightly faster than
libstdc++'s and libc++'s code, but with much less functionality.
The more 'cutting edge C++', the less reusable the program is.

Let say, if the data member is defined as POD types, all the property of the class
should be better.

template<typename T, typename Alloc = std::allocator<T>>
struct ring_deque {
// ....
private:
Elem* m_buf;
size_t m_len;
size_t m_idx_head,m_idx_tail;
};

Another case of the ring buffer is when the size of the buffer is large, e.g. 2Gbytes.

Your ring buffer implement will have many problems. So, your ring buffer is applicable
for smaller size problems and for programs that use newer C++ syntax. Thus, not very reusable.
Loading...