Monday, July 25, 2022

Digest for comp.lang.c++@googlegroups.com - 25 updates in 8 topics

Juha Nieminen <nospam@thanks.invalid>: Jul 25 01:14PM

Some people often object if someone conflates C and C++, as if C++ were
just a pure superset of C, pointing out that they are, in fact, different
languages and, in some aspects, actually incompatible with each other.
 
That got me thinking: What are all the differences between the two
languages, in behavior and meaning, when it comes to their common syntax?
In other words, I'm not here talking about different keywords and different
syntax that compiles in one but not the other. I'm talking about code that
does compile as both C and C++, but will be interpreted or behave
differently depending on which (and this makes them incompatible).
 
Here are some of the things that come to mind. What other things are there?
 
1) A 'const' variable at the global scope will have external linkage by
default in C (unless explicitly made to have internal linkage with
'static'), but internal linkage by default in C++ (unless explicitly made
to have external linkage with 'extern').
 
2) The type of 'A' is int in C, but char in C++.
 
3) This one is really obscure: In C, this:
 
int f(int (*)(), double (*)[3]);
int f(int (*)(char *), double (*)[]);
 
is a valid function declaration, and equivalent to:
 
int f(int (*)(char *), double (*)[3]);
 
(This one is so obscure that I'm certain the vast majority of C programmers,
even experienced ones, would be surprised it even compiles. However, the
example is directly from the C standard, so it's pretty legit.)
 
In C++ the two first lines declare two different functions, taking different
types of parameter. Calling one is not the same thing as calling the other.
"Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Jul 25 04:18PM +0200

On 25 Jul 2022 15:14, Juha Nieminen wrote:
> example is directly from the C standard, so it's pretty legit.)
 
> In C++ the two first lines declare two different functions, taking different
> types of parameter. Calling one is not the same thing as calling the other.
 
Yes, `void` argument list needed to say "no arguments" in C.
 
I guess the most crucial thing is that type punning via union is
well-defined in C, but UB in C++.
 
 
- Alf
Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 06:47PM +0300

25.07.2022 16:14 Juha Nieminen kirjutas:
> syntax that compiles in one but not the other. I'm talking about code that
> does compile as both C and C++, but will be interpreted or behave
> differently depending on which (and this makes them incompatible).
 
This little program outputs 0 when compiled as C, and 1 when compiled as
C++, at least with gcc 10.2. I'm not quite sure if this is meant to be
that way.
 
#include <stdio.h>
#include <stdlib.h>
 
int main() {
 
double x = -0.5;
int y = (int) 2*abs(x);
printf("%d\n", y);
}
"Öö Tiib" <ootiib@hot.ee>: Jul 25 08:51AM -0700

On Monday, 25 July 2022 at 16:14:50 UTC+3, Juha Nieminen wrote:
> example is directly from the C standard, so it's pretty legit.)
 
> In C++ the two first lines declare two different functions, taking different
> types of parameter. Calling one is not the same thing as calling the other.
 
There are not too lot of little things in C that differ from C++ like that:
* C++ has lot of keywords that C does not have or has as macro (bool) or
typedef (wchar_t)
* logical expressions like 1 == 2 result with int (not bool)
* character literals like 'a' are of type int (not char)
* differences with const
* differences with inline
* differences with static
* C has VLAs
* differences in enum type size requirements
 
Those differences can cause same code to compile to different behaviour
in C and C++ silently but it is usually obscure, specially constructed code.
Ben Bacarisse <ben.usenet@bsb.me.uk>: Jul 25 05:14PM +0100


> That got me thinking: What are all the differences between the two
> languages, in behavior and meaning, when it comes to their common
> syntax?
 
Let's see...
 
"abc" is of type char * in C and of type const char * in C++.
 
Then there all the keyword differences. For example, in C, new is an
ordinary ID and in C++ restrict is an ordinary ID. There are quite a
few of these!
 
The rules for compatible pointer types are stronger in C++. Basically,
you can only add a top-level const when passing arguments in C.
 
In C++ function declarations, () means (void), but in C it means an
old-style function with unspecified arguments.
 
In C, at file scope, int x[]; is a "tentative definition" (which will
resolve to int x[] = {0}; if there are no further declarations of x) but
in C++ it's just an error.
 
In C, f( (int[]){1} ), calls f with a temporary array (it's a "compound
literal"), but that's forbidden in C++.
 
Some things that are compound literals in C /are/ valid in C++. For
example:
 
struct s { int v; };
...
f((struct s){1});
 
is ok in both, but add a pointer and you get the same difference as
above:
 
g(&(struct s){1}); // ok in C, not ok in C++
 
And now I'm out of time...
 
> int f(int (*)(char *), double (*)[]);
 
> is a valid function declaration, and equivalent to:
 
> int f(int (*)(char *), double (*)[3]);
 
Yes. This is C's rules for "composite types" in action.
 
 
> In C++ the two first lines declare two different functions, taking different
> types of parameter. Calling one is not the same thing as calling the
> other.
 
However, you can, in fact call the second f with a double (*)[] argument
because of C++'s rules about similar types (at least I think those are
the rules that apply here).
 
--
Ben.
Muttley@dastardlyhq.com: Jul 25 04:19PM

On Mon, 25 Jul 2022 17:14:05 +0100
 
>is ok in both, but add a pointer and you get the same difference as
>above:
 
> g(&(struct s){1}); // ok in C, not ok in C++
 
IOW mostly contorted syntax that was probably never intended to be used but
happens to be legal due to the way the C parser works.
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Jul 25 05:45PM +0100

On Mon, 25 Jul 2022 13:14:35 -0000 (UTC)
> example is directly from the C standard, so it's pretty legit.)
 
> In C++ the two first lines declare two different functions, taking different
> types of parameter. Calling one is not the same thing as calling the other.
 
One thing I don't think mentioned so far, for those who like
low-level twiddling of trivial types: in C, except for bit-fields all
objects are composed of contiguous sequences of one or more bytes,
which thereby comprise an array of bytes in C. In C++ trivially
copyable or standard layout types (ie C-like types) are required to be
comprised of contiguous bytes, but these do not comprise an "array" of
bytes. Hence you can use pointer arithmetic to access and/or modify the
bytes of object entities in C, and this is a fairly common practice for
some low-level work, but not in C++ (save that in C++, when within a
C-like entity you can at least successively increment a byte pointer by
one because in C++ every object, including a byte, can be treated as an
array of size 1).
 
One other related point is that in C a pointer to any narrow character
type is exempt from the strict aliasing rules, whereas in C++ pointers
to signed char are excluded from the exemption.
Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 07:49PM +0300

25.07.2022 18:47 Paavo Helde kirjutas:
>     int y = (int) 2*abs(x);
>     printf("%d\n", y);
> }
 
This example can be actually made a bit simpler, messing with ints is
actually not needed. I think this is now similar to what I stomped on
myself in real code.
 
#include <stdio.h>
#include <stdlib.h>
 
int main() {
 
double x = -0.5;
double y = 2.0 * abs(x);
printf("%g\n", y);
}
"Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Jul 25 02:48PM +0200

This code parses an unsigned decimal integer spec with possible group
delimiters.
 
Since it's intricate it can probably be very much simplified?
 
Result_uint result = 0;
bool overflowed = false;
char ch; // Declared here to be accessible to the
loop's update expression.
Prev_char previous_ch = '\0';
for( p_end = p_first; p_end != p_beyond; previous_ch = ch, ++p_end ) {
ch = *p_end;
WITH_CONST( ch ) {
if constexpr( group_delimiters_allowed ) {
if( ch == group_delimiter ) {
if( previous_ch == group_delimiter ) {
return Ret( no_result,
Error::consecutive_group_delimiters );
}
continue;
}
}
if( not( ch %in% ascii::digits ) ) { return result; }
const Result_uint before = result;
result = 10*result + (ch - '0');
if( result < before ) { overflowed = true; }
}
}
 
... where `WITH_CONST` just adds `const`-ness to the specified name so
that it's provably not modified in that code block, and where the
`Prev_char` type by default is `char`, but can be dummy (optimized away
operations) when code is instantiated to parse without group delimiters.
 
I declared `ch` before the loop so it can be accessed by the `for` loop
head's update expression, so that that expression can handle the
`continue` statement.
 
I guess if one can get rid of the `continue` then one can move the
declaration of `ch` inside the loop and just say directly that it's `const`?
 
- Alf
Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 04:25PM +0300

25.07.2022 15:48 Alf P. Steinbach kirjutas:
 
> I guess if one can get rid of the `continue` then one can move the
> declaration of `ch` inside the loop and just say directly that it's
> `const`?
 
I see your obscure WITH_CONST macro and counter with my obscure
UPDATE_AND_CONTINUE macro:
 
#define UPDATE_AND_CONTINUE(a,b) {a = b; continue;}
 
std::string_view input {p_first, p_beyond-p_first};
Prev_char previous_ch = '\0';
 
for(const char ch: input) {
if (/*...*/) {
if (/*....*/) {
// ...
UPDATE_AND_CONTINUE(previous_ch, ch);
}
}
// ...
UPDATE_AND_CONTINUE(previous_ch, ch);
}
 
 
Who says my macro is uglier than yours?
"Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Jul 25 04:01PM +0200

On 25 Jul 2022 15:25, Paavo Helde wrote:
>     UPDATE_AND_CONTINUE(previous_ch, ch);
> }
 
> Who says my macro is uglier than yours?
 
Heh :-), it's certainly less reusable functionality, but it's an idea.
 
And I like fresh ideas.
 
Just a shame that C++ doesn't yet formally support push and pop of
macros so that one can freely use local macros...
<url:
https://docs.microsoft.com/en-us/cpp/preprocessor/push-macro?view=msvc-170>
<url: https://gcc.gnu.org/onlinedocs/gcc/Push_002fPop-Macro-Pragmas.html>
 
I would also have liked language support for `WITH_CONST`. There's the
whole $ space available for new keywords. It could be `$with_const`.
 
Re the `string_view`:
 
that might seem smart but part of the parsing effect is to produce a
final `p_end`, beyond the number spec, where the caller can continue
higher level parsing (if any). That's also the reason for the boolean
`overflow`. To move the `p_end` beyond the spec even if overflow.
 
Thanks,
 
- Alf
Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 12:20PM +0200

I tried to evaluate the performance of ostringstream with MSVC,
clang++-12 under Linux and g++.
 
#include <iostream>
#include <sstream>
#include <vector>
#include <chrono>
#include <iomanip>
#include <array>
 
using namespace std;
using namespace chrono;
 
int main()
{
constexpr size_t N = 1'000'000;
auto bench = []<typename StringGen>( StringGen stringGen ) -> double
requires requires( StringGen stringGen ) { { stringGen( (size_t)123 )
} -> std::same_as<string>; }
{
vector<string> vs;
vs.reserve( N );
auto start = high_resolution_clock::now();
for( size_t i = N; i--; )
vs.emplace_back( stringGen( i ) );
return (double)(int64_t)duration_cast<nanoseconds>(
high_resolution_clock::now() - start ).count() / N;
};
ostringstream oss;
auto genOss = [&]( size_t x ) -> string
{
oss.str( "" );
oss << setw( 8 ) << setfill( '0' ) << hex << x;
return oss.str();
};
cout << bench( genOss ) << endl;
auto genManual = []( size_t x ) -> string
{
using arr_t = array<char, 8>;
using arr_it = typename arr_t::iterator;
array<char, 8> str;
arr_it it = str.end();
while( x && it > str.begin() )
*--it = (x % 10) + '0',
x /= 10;
for( ; it > str.begin(); *--it = '0' );
return string( str.begin(), str.end() );
};
cout << bench( genManual ) << endl;
}
 
The above program takes about 300ns for each ostringstream-generated
string on my TR3990X under Windows with MSVC 2022, but only 17ns for
my own generator; it's nearly the same with clang-cl 13 under Windows.
Unter Ubuntu with g++ 11 on a 13 yr old Phenom it's abotu 110 and 36.
clang++-12 under Linux (different standard libary than under Windows)
takes 125 vs. 43ns on the same computer.
I won't expect that a stream is that fast like my hand-optimized code,
but I think that this could go even faster with ostringstream under
Linux, and Windows for sure.
"Öö Tiib" <ootiib@hot.ee>: Jul 25 03:51AM -0700

On Monday, 25 July 2022 at 13:20:09 UTC+3, Bonita Montero wrote:
> I tried to evaluate the performance of ostringstream with MSVC,
> clang++-12 under Linux and g++.
 
...
 
> oss << setw( 8 ) << setfill( '0' ) << hex << x;
 
...
 
> --it = (x % 10) + '0', x /= 10;
 
...
 
Output of those programs is likely different? One looks hex ... other decimal.
 
A function hard-coded for narrow case compared with widely dynamically
configurable one (with all those setw, setfill and imbue) will likely win forever.
For fairness compare with std::to_chars() that has narrowed down the
spec of configurability to be easier to make efficient.
Wuns Haerst <Wuns.Haerst@wurstfabrik.at>: Jul 25 01:36PM +0200

Am 25.07.2022 um 12:51 schrieb Öö Tiib:
 
>> --it = (x % 10) + '0', x /= 10;
 
> ...
 
> Output of those programs is likely different? One looks hex ... other decimal.
 
OOOOOOOOOOh, you're right, hex would be even faster.
And with decimal I won't get all digits in the 8 characters.
Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 01:42PM +0200

Am 25.07.2022 um 12:51 schrieb Öö Tiib:
> configurable one (with all those setw, setfill and imbue) will likely win forever.
> For fairness compare with std::to_chars() that has narrowed down the
> spec of configurability to be easier to make efficient.
 
It's obvious that this is faster, but I didn't expect
the streams to be so slow.
 
auto genManual = []( size_t x ) -> string
{
using arr_t = array<char, 8>;
using arr_it = typename arr_t::iterator;
array<char, 8> str;
arr_it it = str.end();
static char const digits[16] = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
while( x && it > str.begin() )
*--it = digits[x % 16],
x /= 16;
for( ; it > str.begin(); *--it = '0' );
return string( str.begin(), str.end() );
};
 
 
This is the right function but it isn't faster
although there are no slow /10-divisions.
Paavo Helde <eesnimi@osa.pri.ee>: Jul 25 03:53PM +0300

25.07.2022 13:20 Bonita Montero kirjutas:
> I tried to evaluate the performance of ostringstream with MSVC,
> clang++-12 under Linux and g++.
[...]
> I won't expect that a stream is that fast like my hand-optimized code,
> but I think that this could go even faster with ostringstream under
> Linux, and Windows for sure.
 
We have covered this before. C++ streams are slow because of
 
* lots of flexibility, i.e.
* lots of virtual function calls
* lots of dynamic allocations
* locale support
* perform both formatting and file output
 
By using a stringstream you have avoided the file output part and
potentially also the locale support part (not sure about that, maybe on
Linux?). The virtual calls and memory allocations are still there. In
other words, you are using a trained carpenter with a chisel for
preparing firewood, of course it will be slow.
 
If you are concerned about performance, you should start with
std::to_chars() as others have already commented.
Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 03:12PM +0200

Am 25.07.2022 um 14:53 schrieb Paavo Helde:
>    * lots of dynamic allocations
>    * locale support
> * perform both formatting and file output
 
Except from the virtual functin calls this might hurt, but that's
still a metter of implementation. The formatting-part wouldn't be
much different than I do that.
 
Vir Campestris <vir.campestris@invalid.invalid>: Jul 25 12:11PM +0100

On 19/07/2022 19:50, Jack Lemmon wrote:
 
> Perhaps you should post this to Thunderbird Newsgroup where they might
> tell you what your mistakes. Thunderbird is pretty robust newsreader.
 
I did. I even raised a formal bug, and found other people reporting the
same thing.
 
Sadly the Thunderbird team seem more interested in new features.
 
I might get around to downloading the source and investigating, but
despite having just retired I don't seem to have the time.
 
Andy
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jul 24 04:55PM -0700

On 7/24/2022 1:20 PM, Chris M. Thomasson wrote:
> a single-producer/multi-consumer queue. If you are interested in sheer
> performance, there are several interesting options. Perhaps I can help
> you. Do you care if you get FIFO, or LIFO?
 
Oh. Reading your original thread.
"Öö Tiib" <ootiib@hot.ee>: Jul 25 03:21AM -0700

On Sunday, 24 July 2022 at 23:17:44 UTC+3, Chris M. Thomasson wrote:
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
> Yikes! You really should have a backoff in there. Do you want your
> worker threads to be running full speed with absolutely nothing to do?
 
Yes. Whatever spinlock should be held for rather short time. Here we are
misnaming infinitely busy poll loop (that is terrible anti-pattern) as
"spinlock".
 
The symptoms from OP that "CPU fan getting a bit load and the OS
getting a big laggy" are because one of cores is full busy warming room.
It is visible in whatever activity monitor or task manager of operating
system and anyone half-savvy suggests to kill and uninstall it right away.
Juha Nieminen <nospam@thanks.invalid>: Jul 25 05:47AM

> Don't bother, i have just posted 3 posts and i will stop it and i will
> not post for a long time
 
You have told that lie in the past many times.
Bonita Montero <Bonita.Montero@gmail.com>: Jul 25 09:04AM +0200

Am 25.07.2022 um 00:57 schrieb Amine Moulay Ramdane:
 
You have a bipolar disorder, i.e. you're manic-depressive.
You could get a much better life on medication.
Juha Nieminen <nospam@thanks.invalid>: Jul 25 05:44AM

> - Community that works to be welcoming, inclusive, and friendly
 
Especially "inclusive" is a warning flag, because in modern political
vocabulary it means pretty much the exact opposite.
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jul 24 05:04PM -0700

On 7/22/2022 7:28 AM, Frederick Virchanza Gotham wrote:
 
> I have one main thread and eight worker threads.
 
> The main thread is reading in data from a file, and at every 8 kilobytes, it distributes the workload evenly to the eight worker threads, so each worker thread processes 1 kB at a time.
 
> By the time all the worker threads have finished processing their kilobyte, the main thread has prepared another 8 kB workload to distribute.
 
 
It depends on how flexible you want to be. Can worker threads begin to
process an 1kb chunk of data _before_ the main thread is finished
getting the 8kb chunk ready, so to speak?
 
Is the 8kb important, or arbitrary?
 
 
> It is very important that the previous workload has been fully processed before the next workload is dished out.
 
So, the main thread cannot read the next 8kb and prepare it for work
_until_ the previous 8kb has been processed?
 
[...]
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Jul 24 05:33PM -0700

On 7/22/2022 7:28 AM, Frederick Virchanza Gotham wrote:
> // same workload. This won't happen if each thread takes
> // milliseconds to execute, and if the thread scheduler takes
> // only microseconds to start another thread going again.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
YIKES! This sounds very fishy.
 
> {
> std::this_thread::sleep_for(std::chrono::milliseconds(1u));
> }
 
^^^^^^^^^^^^^^^^^^^^^^^^
 
Strange.
 
 
 
> sem.acquire();
 
> bitmask_started |= (1u << thread_id);
> assert( 0u == (bitmask_finished & (1u << thread_id)) );
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
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: