• Re: ?Is C++ Dead??

    From Bonita Montero@3:633/10 to All on Wed Mar 18 20:53:42 2026
    Am 18.03.2026 um 20:21 schrieb Lynn McGuire:

    Spinning off threads is easy. Deciding which variables to mutex is not.
    ˙Especially in software hundreds of thousands of lines of of C++ long.

    Code may be that long, but locking scopes are never so long.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Mar 18 13:28:43 2026
    On 3/18/2026 12:53 PM, Bonita Montero wrote:
    Am 18.03.2026 um 20:21 schrieb Lynn McGuire:

    Spinning off threads is easy. Deciding which variables to mutex is
    not. ˙Especially in software hundreds of thousands of lines of of C++
    long.

    Code may be that long, but locking scopes are never so long.


    Ummm.... I have had to debug others code in the past. So, shit happens man!

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Wed Mar 18 21:24:39 2026
    Lynn McGuire <lynnmcguire5@gmail.com> writes:
    On 3/18/2026 1:09 PM, Bonita Montero wrote:
    Am 09.03.2026 um 23:36 schrieb Lynn McGuire:

    If C++ is too complex for a programmer then the programmer is not
    a good programmer.

    I'm a good C++ programmer but I admit that C++ is much less readable
    as for example Rust. And both languages server the same purpose. But
    I still like C++.

    The only exception to that is multiple thread software.˙That stuff
    is dadgum complicated.˙But the multiple thread software is not used
    very often in my circles.

    Most MT-algoritms are easy (embarassingly parallel).

    Spinning off threads is easy. Deciding which variables to mutex is not.

    As someone with four decades of multitheaded programming experience
    at both the lowest levels (OS) and highest levels (applications),
    I strongly disagree.

    It's very easy to determine whether a particular datum is
    subject to concurrent access based on understanding the
    underlying algorithms used.

    Especially in software hundreds of thousands of lines of of C++ long.

    My Windows user interface is well over 500,000 lines of C++ code.

    Ah, windows. Sigh.

    My current project at work is over 3 million lines of C++ code, and
    leverages 64 physical cores using over 120 threads (pthreads)
    with pretty much 100% utilization across all the cores.

    The nice thing about C++ is that if you do it right, all the
    locking is almost always encapsulated within a class (e.g.
    a class member and associated member functions), which naturally
    restricts the scope of the lock to within the class.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lynn McGuire@3:633/10 to All on Wed Mar 18 16:41:53 2026
    On 3/18/2026 4:24 PM, Scott Lurndal wrote:
    Lynn McGuire <lynnmcguire5@gmail.com> writes:
    On 3/18/2026 1:09 PM, Bonita Montero wrote:
    Am 09.03.2026 um 23:36 schrieb Lynn McGuire:

    If C++ is too complex for a programmer then the programmer is not
    a good programmer.

    I'm a good C++ programmer but I admit that C++ is much less readable
    as for example Rust. And both languages server the same purpose. But
    I still like C++.

    The only exception to that is multiple thread software.˙That stuff
    is dadgum complicated.˙But the multiple thread software is not used
    very often in my circles.

    Most MT-algoritms are easy (embarassingly parallel).

    Spinning off threads is easy. Deciding which variables to mutex is not.

    As someone with four decades of multitheaded programming experience
    at both the lowest levels (OS) and highest levels (applications),
    I strongly disagree.

    It's very easy to determine whether a particular datum is
    subject to concurrent access based on understanding the
    underlying algorithms used.

    Especially in software hundreds of thousands of lines of of C++ long.

    My Windows user interface is well over 500,000 lines of C++ code.

    Ah, windows. Sigh.

    My current project at work is over 3 million lines of C++ code, and
    leverages 64 physical cores using over 120 threads (pthreads)
    with pretty much 100% utilization across all the cores.

    Nice ! Good job.

    The nice thing about C++ is that if you do it right, all the
    locking is almost always encapsulated within a class (e.g.
    a class member and associated member functions), which naturally
    restricts the scope of the lock to within the class.

    It is always, "if you do it right".

    Lynn


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lynn McGuire@3:633/10 to All on Wed Mar 18 16:45:08 2026
    On 3/18/2026 2:53 PM, Bonita Montero wrote:
    Am 18.03.2026 um 20:21 schrieb Lynn McGuire:

    Spinning off threads is easy. Deciding which variables to mutex is
    not. ˙Especially in software hundreds of thousands of lines of of C++
    long.

    Code may be that long, but locking scopes are never so long.

    One of these days I would love to learn more about threads and locking
    when life slows down a little bit.

    Lynn


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Mar 18 19:58:38 2026
    On 3/18/2026 2:41 PM, Lynn McGuire wrote:
    On 3/18/2026 4:24 PM, Scott Lurndal wrote:
    Lynn McGuire <lynnmcguire5@gmail.com> writes:
    On 3/18/2026 1:09 PM, Bonita Montero wrote:
    Am 09.03.2026 um 23:36 schrieb Lynn McGuire:

    If C++ is too complex for a programmer then the programmer is not
    a good programmer.

    I'm a good C++ programmer but I admit that C++ is much less readable
    as for example Rust. And both languages server the same purpose. But
    I still like C++.

    The only exception to that is multiple thread software.˙That stuff
    is dadgum complicated.˙But the multiple thread software is not used
    very often in my circles.

    Most MT-algoritms are easy (embarassingly parallel).

    Spinning off threads is easy.˙ Deciding which variables to mutex is not.

    As someone with four decades of multitheaded programming experience
    at both the lowest levels (OS) and highest levels (applications),
    I strongly disagree.

    It's very easy to determine whether a particular datum is
    subject to concurrent access based on understanding the
    underlying algorithms used.

    ˙ Especially in software hundreds of thousands of lines of of C++ long.

    My Windows user interface is well over 500,000 lines of C++ code.

    Ah, windows.˙ Sigh.

    My current project at work is over 3 million lines of C++ code, and
    leverages 64 physical cores using over 120 threads (pthreads)
    with pretty much 100% utilization across all the cores.

    Nice !˙ Good job.

    The nice thing about C++ is that if you do it right, all the
    locking is almost always encapsulated within a class (e.g.
    a class member and associated member functions), which naturally
    restricts the scope of the lock to within the class.

    It is always, "if you do it right".

    If you decide to use threads. Make sure to learn about mutex and condvar first! Then learn about embarrassingly parallel, where no mutex and
    condvar are "necessarily" needed. Then ponder on lock-free.

    Make sure to never have to use lock-free unless you absolutely have to!

    Fwiw, I used to converse with David Butenhof back on
    comp.programming.threads. Nice guy, and wrote a nice book to read before
    you even go into C++ threads, perhaps. Well... ? I remember he was
    pondering on writing another book, cooking with posix threads.

    https://www.amazon.com/Programming-POSIX-Threads-David-Butenhof/dp/0201633922

    :^) Real all of it, then ponder on how C++ is there ready willing and able.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lynn McGuire@3:633/10 to All on Thu Mar 19 00:33:44 2026
    On 3/18/2026 9:58 PM, Chris M. Thomasson wrote:
    On 3/18/2026 2:41 PM, Lynn McGuire wrote:
    On 3/18/2026 4:24 PM, Scott Lurndal wrote:
    Lynn McGuire <lynnmcguire5@gmail.com> writes:
    On 3/18/2026 1:09 PM, Bonita Montero wrote:
    Am 09.03.2026 um 23:36 schrieb Lynn McGuire:

    If C++ is too complex for a programmer then the programmer is not
    a good programmer.

    I'm a good C++ programmer but I admit that C++ is much less readable >>>>> as for example Rust. And both languages server the same purpose. But >>>>> I still like C++.

    The only exception to that is multiple thread software.˙That stuff >>>>>> is dadgum complicated.˙But the multiple thread software is not used >>>>>> very often in my circles.

    Most MT-algoritms are easy (embarassingly parallel).

    Spinning off threads is easy.˙ Deciding which variables to mutex is
    not.

    As someone with four decades of multitheaded programming experience
    at both the lowest levels (OS) and highest levels (applications),
    I strongly disagree.

    It's very easy to determine whether a particular datum is
    subject to concurrent access based on understanding the
    underlying algorithms used.

    ˙ Especially in software hundreds of thousands of lines of of C++ long. >>>>
    My Windows user interface is well over 500,000 lines of C++ code.

    Ah, windows.˙ Sigh.

    My current project at work is over 3 million lines of C++ code, and
    leverages 64 physical cores using over 120 threads (pthreads)
    with pretty much 100% utilization across all the cores.

    Nice !˙ Good job.

    The nice thing about C++ is that if you do it right, all the
    locking is almost always encapsulated within a class (e.g.
    a class member and associated member functions), which naturally
    restricts the scope of the lock to within the class.

    It is always, "if you do it right".

    If you decide to use threads. Make sure to learn about mutex and condvar first! Then learn about embarrassingly parallel, where no mutex and
    condvar are "necessarily" needed. Then ponder on lock-free.

    Make sure to never have to use lock-free unless you absolutely have to!

    Fwiw, I used to converse with David Butenhof back on comp.programming.threads. Nice guy, and wrote a nice book to read before
    you even go into C++ threads, perhaps. Well... ? I remember he was
    pondering on writing another book, cooking with posix threads.

    https://www.amazon.com/Programming-POSIX-Threads-David-Butenhof/ dp/0201633922

    :^) Real all of it, then ponder on how C++ is there ready willing and able.

    I have four threads in my Windows user interface to make the startup
    faster. But the variables do not cross the thread boundaries.

    I have a copy of "C++ Concurrency in Action: Practical Multithreading"
    First Edition by Anthony Williams. I have yet to read it though.

    https://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770/

    Lynn


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Mar 18 22:50:14 2026
    On 3/18/2026 10:33 PM, Lynn McGuire wrote:
    [...]
    I have a copy of "C++ Concurrency in Action: Practical Multithreading"
    First Edition by Anthony Williams.˙ I have yet to read it though.

    https://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/ dp/1933988770/

    That is another good one. I used to converse with him quite a bit as well.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thu Mar 19 10:20:26 2026
    On 18/03/2026 22:24, Scott Lurndal wrote:
    Lynn McGuire <lynnmcguire5@gmail.com> writes:
    On 3/18/2026 1:09 PM, Bonita Montero wrote:
    Am 09.03.2026 um 23:36 schrieb Lynn McGuire:

    If C++ is too complex for a programmer then the programmer is not
    a good programmer.

    I'm a good C++ programmer but I admit that C++ is much less readable
    as for example Rust. And both languages server the same purpose. But
    I still like C++.

    The only exception to that is multiple thread software.˙That stuff
    is dadgum complicated.˙But the multiple thread software is not used
    very often in my circles.

    Most MT-algoritms are easy (embarassingly parallel).

    Spinning off threads is easy. Deciding which variables to mutex is not.

    As someone with four decades of multitheaded programming experience
    at both the lowest levels (OS) and highest levels (applications),
    I strongly disagree.

    It's very easy to determine whether a particular datum is
    subject to concurrent access based on understanding the
    underlying algorithms used.


    It can be easy to determine what data needs protected by mutexes, but
    not nearly as easy to determine when the mutexes should be locked and
    unlocked in sequences that are maximally efficient. If a thread takes a
    lock, and is doing two different things with the data, should it release
    the lock and re-take it in the middle? Doing so costs time for that
    thread, as the release and re-lock is not free. But /not/ doing so
    could hinder progress for another thread. Then you have questions about
    how fine-grained your mutexes should be, and how to coordinate cases
    where you need multiple mutexes. And of course you have the bigger architectural questions - should the data be shared and locked by a
    mutex, or use a message queue, or atomics, or duplicate copies of the
    data, or some other sharing and synchronisation solution?

    So yes, determining what data to protect is usually easy. Determining
    how best to do that is the fun part.



    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Thu Mar 19 10:28:09 2026
    Am 18.03.2026 um 22:41 schrieb Lynn McGuire:

    It is always, "if you do it right".

    Handling locks is mostly very easy. But sometimes it is necessary
    to handle the locks efficiently. And sometimes that's very tricky.


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Thu Mar 19 10:40:59 2026
    Am 19.03.2026 um 03:58 schrieb Chris M. Thomasson:

    If you decide to use threads. Make sure to learn about mutex and condvar first! Then learn about embarrassingly parallel, where no mutex and
    condvar are "necessarily" needed. Then ponder on lock-free.

    Embarassingly parallel doesn't mean that no mutexes or
    condvars are necessary, just that parallelizing was easy.

    Make sure to never have to use lock-free unless you absolutely have to!

    There are only two definitive lock-free algorithms: lock-free stacks
    and lock free queues. Lock-free stacks have a very rare usage. Lock
    -free queues have the disadvantage that they have to be polled - which
    is an absolute noo-go - and that can't be resized at runtime.
    But when you have a queue guarded with a mutex and a condvar that's
    mostly sufficient because there's rarely an overlap of the access
    between producers and consumers since enqueuing and dequeuing usually
    takes a minimum of computation time.
    On Windows you could use deques. Although the Window's deques are not
    very smart since they work with chunks of one element, they don't with-
    draw every node which is being poped; instead they go to a standby list
    so that they can be recycled very fast. This is allowed by the deque
    -interface since it has a shrink_to_fit() call.
    Libstdc++ (clang++ and g++ on all OSs except macOS) allocates the items
    at chunks up zo 512 bytes. This makes mostly linear memory accesses and allocations and deallocations to happen not often.
    I wasn't satisfied with that solution so that I wrote a simple deque -replacement. Acces is supported not by iterators (to much work) but
    with a functional interface.

    Here's the code:

    #pragma once
    #include <array>
    #include <vector>
    #include <memory>
    #include <iterator>
    #include <algorithm>
    #include <concepts>
    #include <cassert>
    #include <optional>
    #include "defer.hpp"
    #include "ndi.hpp"
    #include "cacheline.hpp"
    #undef min

    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunqualified-std-cast-call"
    #pragma clang diagnostic ignored "-Wdangling-else"
    #pragma clang diagnostic ignored "-Wlogical-op-parentheses"
    #endif

    template<typename T>
    struct xdeque
    {
    xdeque();
    xdeque( xdeque<T> &&other ) noexcept;
    //xdeque( const xdeque<T> &other );
    ~xdeque();
    xdeque<T> &operator =( xdeque<T> &&other ) noexcept;
    //xdeque<T> &operator =( const xdeque<T> &other ) noexcept;
    template<typename ... Params>
    T &emplace_back( Params &&... params );
    //void emplace_back_row( T &&value, size_t n );
    void pop_front() noexcept;
    std::optional<T> pop_back() noexcept;
    T &front() noexcept;
    const T &front() const noexcept;
    size_t size() const noexcept;
    bool empty() const noexcept;
    void clear() noexcept;
    void shrink_to_fit() noexcept;
    template<typename Consumer>
    void consume( Consumer consumer );
    template<typename Consumer>
    void consume( Consumer consumer ) const;
    private:
    template<size_t N>
    struct n_chunk;
    template<size_t N>
    using n_chunk_ptr = std::unique_ptr<n_chunk<N>>;
    template<size_t N>
    struct n_chunk : std::array<tndi<T>, N>
    {
    n_chunk_ptr<N> next;
    };
    static constexpr ptrdiff_t
    XN_CHUNK = (ptrdiff_t)(512 - sizeof(n_chunk<1>)) / (ptrdiff_t)sizeof(T) + 1,
    N_CHUNK = XN_CHUNK >= 1 ? XN_CHUNK : 1;
    using chunk = n_chunk<N_CHUNK>;
    using chunk_ptr = n_chunk_ptr<N_CHUNK>;
    using chunk_ptrs = std::vector<chunk_ptr>;
    chunk_ptrs m_chunks;
    chunk_ptr m_firstStandby;
    using chunks_it = typename chunk_ptrs::iterator;
    using chunk_it = typename chunk::iterator;
    struct pointer
    {
    chunks_it itChunks;
    chunk_it itChunk;
    } m_begin, m_end;
    void shrinkChunks() noexcept;
    #if !defined(NDEBUG)
    bool checkQueue() noexcept;
    #endif
    };

    template<typename T>
    void xdeque<T>::shrinkChunks() noexcept
    {
    using namespace std;
    defer exitCheck( [&] { assert(checkQueue()); } );
    if( (size_t)(m_begin.itChunks - m_chunks.begin()) * 2 <= m_chunks.size() )
    return;
    std::move_iterator
    itBegin( m_begin.itChunks ),
    itEnd( m_chunks.end() );
    size_t toEnd = m_end.itChunks == m_chunks.end();
    m_chunks.resize( copy( itBegin, itEnd, m_chunks.begin() ) - m_chunks.begin() );
    m_begin.itChunks = m_chunks.begin();
    m_end.itChunks = m_chunks.end() - (!toEnd && m_chunks.size());
    }

    #if !defined(NDEBUG)
    template<typename T>
    bool xdeque<T>::checkQueue() noexcept
    {
    if( m_begin.itChunks > m_end.itChunks )
    return false;
    if( m_begin.itChunks == m_chunks.end() )
    return m_end.itChunks == m_chunks.end();
    if( m_begin.itChunks == m_end.itChunks )
    return m_begin.itChunk < m_end.itChunk;
    if( m_end.itChunks != m_chunks.end() && (m_end.itChunk == (*m_end.itChunks)->begin() || m_end.itChunk == (*m_end.itChunks)->end()) )
    return false;
    if( m_begin.itChunk < (*m_begin.itChunks)->begin() || m_begin.itChunk
    = (*m_begin.itChunks)->end() )
    return false;
    return true;
    }
    #endif

    template<typename T>
    xdeque<T>::xdeque() :
    m_begin( m_chunks.begin(), chunk_it() ),
    m_end( m_chunks.end(), chunk_it() )
    {
    }

    template<typename T>
    xdeque<T>::xdeque( xdeque<T> &&other ) noexcept :
    m_chunks( std::move( other.m_chunks ) ),
    m_firstStandby( std::move( other.m_firstStandby ) ),
    m_begin( other.m_begin ),
    m_end( other.m_end )
    {
    other.m_chunks.resize( 0 );
    other.m_begin.itChunks = other.m_chunks.begin();
    other.m_end.itChunks = other.m_chunks.end();
    }

    template<typename T>
    xdeque<T> &xdeque<T>::operator =( xdeque<T> &&other ) noexcept
    {
    using namespace std;
    m_chunks = move( other.m_chunks );
    m_firstStandby = move( other.m_firstStandby );
    m_begin = other.m_begin;
    m_end = other.m_end;
    other.m_chunks.resize( 0 );
    other.m_begin.itChunks = other.m_chunks.begin();
    other.m_end.itChunks = other.m_chunks.end();
    return *this;
    }

    template<typename T>
    inline xdeque<T>::~xdeque()
    {
    clear();
    }

    template<typename T>
    template<typename ... Params>
    T &xdeque<T>::emplace_back( Params &&... params )
    {
    using namespace std;
    defer exitCheck( [&] { assert(checkQueue()); } );
    defer unchunk( [&]
    {
    if( m_end.itChunks == m_chunks.end() || m_end.itChunk >
    (*m_end.itChunks)->begin() )
    return;
    chunk_ptr lastEmpty = move( *m_end.itChunks );
    lastEmpty->next = move( m_firstStandby );
    m_firstStandby = move( lastEmpty );
    m_chunks.pop_back();
    assert(checkQueue());
    } );
    if( m_end.itChunks == m_chunks.end() )
    {
    bool wasEmpty = m_begin.itChunks == m_chunks.end();
    chunk_ptr append = move( m_firstStandby );
    if( append )
    m_firstStandby = move( append->next );
    else
    append = make_cl_unique<chunk>();
    defer unappend( [&]
    {
    append->next = move( m_firstStandby );
    m_firstStandby = move( append );
    } );
    size_t iBegin = m_begin.itChunks - m_chunks.begin();
    m_chunks.emplace_back( move( append ) );
    unappend.disable();
    m_begin.itChunks = m_chunks.begin() + iBegin;
    m_end.itChunks = m_chunks.end() - 1;
    m_end.itChunk = (*m_end.itChunks)->begin();
    if( wasEmpty )
    m_begin.itChunk = (*m_begin.itChunks)->begin();
    }
    T &value = m_end.itChunk->emplace( forward<Params>( params ) ... );
    unchunk.disable();
    if( ++m_end.itChunk == (*m_end.itChunks)->end() )
    m_end.itChunks = m_chunks.end();
    return value;
    }

    template<typename T>
    void xdeque<T>::pop_front() noexcept
    {
    if( empty() )
    return;
    defer shrink( [&] { shrinkChunks(); } );
    m_begin.itChunk++->destruct();
    auto remove = [this]
    {
    chunk_ptr firstEmpty = move( *m_begin.itChunks );
    firstEmpty->next = move( m_firstStandby );
    m_firstStandby = move( firstEmpty );
    };
    if( m_begin.itChunks != m_end.itChunks )
    {
    if( m_begin.itChunk != (*m_begin.itChunks)->end() )
    return;
    remove();
    if( ++m_begin.itChunks != m_chunks.end() )
    m_begin.itChunk = (*m_begin.itChunks)->begin();
    return;
    }
    if( m_begin.itChunk != m_end.itChunk )
    return;
    remove();
    m_begin.itChunks = m_chunks.end();
    m_end.itChunks = m_chunks.end();
    }

    template<typename T>
    std::optional<T> xdeque<T>::pop_back() noexcept
    {
    using namespace std;
    if( empty() )
    return nullopt;
    defer shrink( [&] { shrinkChunks(); } );
    pointer end = m_end;
    if( end.itChunks == m_chunks.end() )
    {
    end.itChunks = m_chunks.end() - 1;
    end.itChunk = (*end.itChunks)->end();
    }
    T value( move( *--end.itChunk ) );
    end.itChunk->destruct();
    m_end = end;
    bool singleChunk = m_end.itChunks == m_begin.itChunks;
    if( singleChunk && m_end.itChunk != m_begin.itChunk ||
    !singleChunk && m_end.itChunk != (*m_end.itChunks)->begin() )
    return value;
    chunk_ptr lastEmpty = move( *m_end.itChunks );
    lastEmpty->next = move( m_firstStandby );
    m_firstStandby = move( lastEmpty );
    m_chunks.pop_back();
    m_end.itChunks = m_chunks.end();
    if( singleChunk )
    m_begin.itChunks = m_chunks.end();
    return value;
    }

    template<typename T>
    inline T &xdeque<T>::front() noexcept
    {
    assert(m_begin.itChunks != m_chunks.end());
    return *m_begin.itChunk;
    }

    template<typename T>
    inline const T &xdeque<T>::front() const noexcept
    {
    assert(m_begin.itChunks != m_chunks.end());
    return *m_begin.itChunk;
    }

    template<typename T>
    size_t xdeque<T>::size() const noexcept
    {
    if( m_begin.itChunks == m_chunks.end() )
    return 0;
    size_t n = (m_end.itChunks - m_begin.itChunks) * N_CHUNK;
    if( m_end.itChunks != m_chunks.end() )
    n += m_end.itChunk - (*m_end.itChunks)->begin();
    n -= m_begin.itChunk - (*m_begin.itChunks)->begin();
    return n;
    }

    template<typename T>
    inline bool xdeque<T>::empty() const noexcept
    {
    return m_begin.itChunks == m_chunks.end();
    }

    template<typename T>
    void xdeque<T>::clear() noexcept
    {
    if( empty() )
    return;
    defer shrink( [&] { shrinkChunks(); } );
    for( ; ; )
    {
    chunk_it itChunkEnd;
    if( m_begin.itChunks != m_chunks.end() - 1 || m_end.itChunks ==
    m_chunks.end() )
    itChunkEnd = (*m_begin.itChunks)->end();
    else
    itChunkEnd = m_end.itChunk;
    do
    m_begin.itChunk->destruct();
    while( ++m_begin.itChunk != itChunkEnd );
    chunk_ptr firstEmpty = move( *m_begin.itChunks );
    firstEmpty->next = move( m_firstStandby );
    m_firstStandby = move( firstEmpty );
    if( ++m_begin.itChunks == m_chunks.end() )
    {
    m_end.itChunks = m_chunks.end();
    return;
    }
    m_begin.itChunk = (*m_begin.itChunks)->begin();
    }
    }

    template<typename T>
    inline void xdeque<T>::shrink_to_fit() noexcept
    {
    m_firstStandby.reset();
    }

    template<typename T>
    template<typename Consumer>
    void xdeque<T>::consume( Consumer consumer )
    {
    using namespace std;
    if( m_begin.itChunks == m_chunks.end() )
    return;
    defer shrink( [&] { shrinkChunks(); } );
    int ret = 1;
    do
    {
    size_t nChunk;
    if( m_begin.itChunks != m_end.itChunks )
    nChunk = (*m_begin.itChunks)->end() - m_begin.itChunk;
    else
    nChunk = m_end.itChunk - m_begin.itChunk;
    do
    {
    if constexpr( requires() { (int)consumer( move( **m_begin.itChunk )
    ); } )
    ret = (int)consumer( move( **m_begin.itChunk ) );
    else
    consumer( move( **m_begin.itChunk ) );
    if( ret < 0 )
    return;
    m_begin.itChunk++->destruct();
    } while( --nChunk && ret );
    if( nChunk )
    return;
    chunk_ptr emptied = move( *m_begin.itChunks );
    emptied->next = move( m_firstStandby );
    m_firstStandby = move( emptied );
    if( m_begin.itChunks != m_chunks.end() - 1 )
    m_begin.itChunk = (*++m_begin.itChunks)->begin();
    else
    {
    m_begin.itChunks = m_chunks.end();
    m_end.itChunks = m_chunks.end();
    return;
    }
    } while( ret );
    }

    template<typename T>
    template<typename Consumer>
    void xdeque<T>::consume( Consumer consumer ) const
    {
    const_cast<xdeque<T> &>( *this ).consume( [&]( const T &value ) { return observer( value ); } );
    }

    #if !defined(NDEBUG)
    template
    struct xdeque<int>;
    #endif

    #if defined(__clang__)
    #pragma clang diagnostic pop
    #endif


    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Mar 19 12:26:19 2026
    On 3/19/2026 2:40 AM, Bonita Montero wrote:
    Am 19.03.2026 um 03:58 schrieb Chris M. Thomasson:

    If you decide to use threads. Make sure to learn about mutex and
    condvar first! Then learn about embarrassingly parallel, where no
    mutex and condvar are "necessarily" needed. Then ponder on lock-free.

    Embarassingly parallel doesn't mean that no mutexes or
    condvars are necessary, just that parallelizing was easy.

    Notice the quotes around "necessarily" ? :^)




    Make sure to never have to use lock-free unless you absolutely have to!

    There are only two definitive lock-free algorithms: lock-free stacks
    and lock free queues.

    Oh really? Sigh.

    [...]

    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Mar 19 12:31:57 2026
    On 3/19/2026 2:20 AM, David Brown wrote:
    On 18/03/2026 22:24, Scott Lurndal wrote:
    Lynn McGuire <lynnmcguire5@gmail.com> writes:
    On 3/18/2026 1:09 PM, Bonita Montero wrote:
    Am 09.03.2026 um 23:36 schrieb Lynn McGuire:

    If C++ is too complex for a programmer then the programmer is not
    a good programmer.

    I'm a good C++ programmer but I admit that C++ is much less readable
    as for example Rust. And both languages server the same purpose. But
    I still like C++.

    The only exception to that is multiple thread software.˙That stuff
    is dadgum complicated.˙But the multiple thread software is not used
    very often in my circles.

    Most MT-algoritms are easy (embarassingly parallel).

    Spinning off threads is easy.˙ Deciding which variables to mutex is not.

    As someone with four decades of multitheaded programming experience
    at both the lowest levels (OS) and highest levels (applications),
    I strongly disagree.

    It's very easy to determine whether a particular datum is
    subject to concurrent access based on understanding the
    underlying algorithms used.


    It can be easy to determine what data needs protected by mutexes, but
    not nearly as easy to determine when the mutexes should be locked and unlocked in sequences that are maximally efficient.˙ If a thread takes a lock, and is doing two different things with the data, should it release
    the lock and re-take it in the middle?

    Shit man. TRY to never execute a call back into unknown user code while holding a lock. Unless, well, shit. never... ;^) I have had to debug
    nightmare scenarios! Ouch.



    Doing so costs time for that
    thread, as the release and re-lock is not free.˙ But /not/ doing so
    could hinder progress for another thread.˙ Then you have questions about
    how fine-grained your mutexes should be, and how to coordinate cases
    where you need multiple mutexes.˙ And of course you have the bigger architectural questions - should the data be shared and locked by a
    mutex, or use a message queue, or atomics, or duplicate copies of the
    data, or some other sharing and synchronisation solution?

    So yes, determining what data to protect is usually easy.˙ Determining
    how best to do that is the fun part.




    --- PyGate Linux v1.5.13
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)