Sunday, February 5, 2023

Digest for comp.lang.c++@googlegroups.com - 5 updates in 2 topics

"Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Feb 05 03:37PM +0100

On 2023-02-04 10:32 PM, Alf P. Steinbach wrote:
 
>         auto access() -> Access                 { return Access( *this ); }
>         auto access() const -> const Access     { return Access( *this ); }
>     };
 
Right after I went to bed last night it hit me that the `const` `access`
function couldn't possibly work as written, because the `Access`
constructor it calls tries to initialize a `Shared_queue_*` with a
`const Shared_queue_*`.
 
I haven't yet tried to call it (functions in templates can be very wrong
so that they wouldn't compile if used, as long as they're not actually
used), but after two cups of coffee I now think the same.
 
I saw two possible solutions before I went to sleep:
 
* use a union for the proxy object's pointer-to-queue, or
* use separate proxy types for the mutable and const cases.
 
I am not at all sure that type punning `const T*` and `T*` via a union,
even if it is dynamically safe and correct from a machine code point of
view, is formally allowed in C++?
 
With separate proxy types a `Mutable_access_` can inherit from a
`Const_access_` and have the pointer type or constness thereof as a
template parameter, but though it's type-wise clean it also feels like a
kludge; complex. Furthermore the `const` on the return type will then
not matter. So is it that with formally correct C++ I have not run into
a case where `const` on a by-value return type is reasonable?
 
 
- Alf
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Feb 05 12:49PM -0800

On 2/4/2023 1:32 PM, Alf P. Steinbach wrote:
>                 m_p_queue->m_queue.push( move( item ) );
>                 if( notify ) { m_p_queue->m_condition.notify_one(); }
>             }
[...]
 
Signalling a condition variable while the mutex is locked is not that
efficient unless the condvar has wait morphing. God, I have not thought
about wait morphing for a long time. Thanks for making me think of it!
"Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Feb 05 10:39PM +0100

On 2023-02-05 9:49 PM, Chris M. Thomasson wrote:
 
> Signalling a condition variable while the mutex is locked is not that
> efficient unless the condvar has wait morphing. God, I have not thought
> about wait morphing for a long time. Thanks for making me think of it!
 
Thanks. I don't know yet how to handle that with the RAII-based access
scheme. Perhaps set a count of number of signals to issue then do that
in the Access destructor.
 
All this C++ multithreading is fairly new to me.
 
Previous experience mainly limited to (1) implementing multitasking in
6502 or something assembly at school in 1984 or so, using an HP 64000
development system (that was really really cool!), and implementing
`longjmp` based coroutines in a very very unsafe way in the 1990's,
unfortunately with the code picked up and used by a hospital in New
York, which made me really scared of what I might be responsible for.
Oh, and I forgot, also at school in 1984 or so, an Othello game where
the computer found its next most likely moves while the user was
thinking of his/her move. Due to the apparently instant moves of the
program (it had had much time to think, invisibly) my teacher at first
refused to believe it was not cheating of some kind; I had to show the
debug output of the thinking process on a separate terminal...
 
- Alf
"Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Feb 05 10:45PM +0100

On 2023-02-05 3:37 PM, Alf P. Steinbach wrote:
> kludge; complex. Furthermore the `const` on the return type will then
> not matter. So is it that with formally correct C++ I have not run into
> a case where `const` on a by-value return type is reasonable?
 
I landed on a simple type safe solution with a bit of overhead.
 
Namely using a `std::variant` for the proxy's pointer to its referent.
 
 
template< class Item >
class Shared_queue_
{
queue<Item> m_queue;
 
mutable mutex m_mutex;
mutable condition_variable m_condition;
 
auto length() const -> int { return int_size_of( m_queue ); }
auto is_empty() const -> bool { return m_queue.empty(); }
 
public:
class Access:
public Movable
{
friend class Shared_queue_;
using Q_ptr = variant<Shared_queue_*, const Shared_queue_*>;
 
Q_ptr m_p_queue;
Mutex_lock m_mutex_lock;
 
Access( Shared_queue_& q ): m_p_queue( &q ), m_mutex_lock(
q.m_mutex ) {}
Access( const Shared_queue_& q ): m_p_queue( &q ),
m_mutex_lock( q.m_mutex ) {}
 
auto shared_queue() -> Shared_queue_& { return
*get<Shared_queue_*>( m_p_queue ); }
 
auto shared_queue() const
-> const Shared_queue_&
{ return *(m_p_queue.index() == 0? get<0>( m_p_queue ) :
get<1>( m_p_queue )); }
 
public:
auto length() const -> int { return
shared_queue().length(); }
auto is_empty() const -> bool { return
shared_queue().is_empty(); }
 
void wait_for_items()
{
if( not is_empty() ) { return; }
shared_queue().m_condition.wait( m_mutex_lock, [&]() ->
bool { return not is_empty(); } );
}
 
auto wait_for_items_for( const Duration duration )
-> bool
{
if( not is_empty() ) { return true; }
// TODO: handle possible overflow in
`steady_clock::now() + duration`.
const bool timed_out = not
shared_queue().m_condition.wait_for(
m_mutex_lock, duration, [&]() -> bool { return not
is_empty(); }
);
return not timed_out;
}
 
void enq( Item item, const bool notify = true )
{
shared_queue().m_queue.push( move( item ) );
if( notify ) { shared_queue().m_condition.notify_one(); }
}
 
auto deq() -> Item
{
wait_for_items();
hopefully( not is_empty() )
or FSM_FAIL( "Waited for items but got none;
shared_queue is empty." );
return popped_front_of( shared_queue().m_queue );
}
};
 
auto access() -> Access { return Access( *this ); }
auto access() const -> const Access { return Access( *this ); }
};
 
- Alf
David Brown <david.brown@hesbynett.no>: Feb 05 11:42AM +0100


> Ok, that makes sense. Though I wonder - given the main C compilers used now
> are also C++ compilers - why they don't just silently allow it in C? Would
> there be any wierd or unfortunate side effects in C if it was?
 
Most C++ compilers have a closely related C compiler that is part of the
same overall compiler project, but the front ends for C and C++ are
usually separate programs. So major C++ compilers such as gcc, clang,
MSVC, ICC all support C within the same overall toolchain. But it is
normal that the front-end parsers are separate, even though the
middle-end optimisers and back-end code generators are shared.
 
On the other hand, the vast majority of C compilers are /not/ C++
compilers. With a bit of googling, you'll probably find a couple of
dozen C compilers available for PC's (Linux and/or Windows) freely
available, and only a few of them will also have C++ compilers. Go over
to the embedded world and you can find hundreds of C compilers, and only
perhaps 5% of them support C++.
 
If you look at hobby projects, one-man toolchains and niche compilers, I
have never heard of any that support C++, while a working C compiler is
something many people have written.
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page.
To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com.

No comments: