Tuesday, September 15, 2020

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

Ben Bacarisse <ben.usenet@bsb.me.uk>: Sep 15 01:30AM +0100


> Juha Nieminen to Anton Shepelev:
<cut>
> to do it, but here is my attempt, which did not come quite
> from the tips of my fingers:
 
> int **p[100];
 
It's not really the point of the thread, but that's an array of pointers
to pointers to int. A pointer to an array must use parentheses,
(*p)[...], so that the "pointer to" is read before the "array of" bit:
 
int (*p)[][100];
 
> one learns its logic, and even then it reamains at a
> considerable angle with human intuition. But is not this
> syntax the same in C++?
 
Yes, but in most cases a C++ program would use a different type
altogether.
 
>> programming in C (or C++) for decades. I kid you not.
 
> Not to know that a language supports such a basic data
> structure?
 
I think the point is that few know you can have a "pointer to array"
type. Pointers to the start of an array (which will have the same base
type as the array) are ubiquitous in C but pointers to an array type are
much rarer.
 
<cut>
> typedef void ( *f_signal)( int sig, f_disp disp );
 
> And again, as far as I know, this declaration syntax is as
> essential part of C++ as it is of C. Why mention it?
 
It's no longer "essential" as C++ has other syntax. For example:
 
auto signal(int sig, auto (*disp)(int) -> void) -> auto (*)(int) -> void;
 
<cut>
--
Ben.
Juha Nieminen <nospam@thanks.invalid>: Sep 15 07:28AM

> to pointers to int. A pointer to an array must use parentheses,
> (*p)[...], so that the "pointer to" is read before the "array of" bit:
 
> int (*p)[][100];
 
Yeah, not really the point of the thread, but this beautifully demonstrates
my point.
 
The correct syntax for "pointer to a two-dimensional array of int, where
the second dimension is 100" is:
 
int (*p)[100];
 
Although, technically speaking, that's just "a pointer to an array of
int of size 100", but since a "pointer to value" doubles as a "pointer
to an array of values", that likewise doubles as a pointer to a
two-dimensional array.
 
While 'p' above is technically speaking "a pointer to an array of
size 100", the difference with a mere "int *p;" is what it dereferences
to, and how it behaves in terms of arithmetic. More precisely:
 
int *p1; // pointer to int (or int array)
int (*p2)[100]; // pointer to an int[100] (or array of such)
 
sizeof(*p1) == sizeof(int);
sizeof(*p2) == 100*sizeof(int);
 
++p1; // will increment p1 by sizeof(int)
++p2; // will increment p2 by 100*sizeof(int)
 
A two-dimensional array, like "int table[200][100];" is essentially just
"an array of 200 int[100] elements", which is why you can have a pointer
that points to int[100] and have it point to such an array.
 
It's perhaps understandable, but telling, that even many experienced
C (and C++) programmers don't know this, even after decades.
 
Incidentally in C++ you can have a reference to an actual 2-dimensional
array, where both dimensions are specified:
 
int (&r)[200][100];
 
The semantics of this would require another essay to explain.
Keith Thompson <Keith.S.Thompson+u@gmail.com>: Sep 15 01:01AM -0700

> int of size 100", but since a "pointer to value" doubles as a "pointer
> to an array of values", that likewise doubles as a pointer to a
> two-dimensional array.
 
So, technically speaking,
 
int (*p)[100];
 
is *not* the correct syntax for "pointer to a two-dimensional array of
int, where the second dimension is 100".
 
In fact the correct syntax for that is:
 
int (*p)[][100];
 
(Yes, I cheated and used cdecl.)
 
The distinction between arrays and pointers is muddy enough without
glossing over the very real differences.
 
[...]
 
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
Working, but not speaking, for Philips Healthcare
void Void(void) { Void(); } /* The recursive call of the void */
Paavo Helde <eesnimi@osa.pri.ee>: Sep 15 12:25PM +0300

15.09.2020 02:15 Anton Shepelev kirjutas:
 
> Not at all, because neither of those languages is a near
> subset of C++, which has all the complexity of C and tons
> more to boot.
 
These complexities do not add. When one programs in C++, one takes care
to actively *avoid* unnecessarily complex subsets of C (and most
probably also several subsets of C++, but that's beside the point).
 
Foe example, std::string is much more easier to use correctly than
strcat(). By using std::vector and std::array one can easily avoid all
syntax and behavior complexities of raw arrays. The list goes on.
Bart <bc@freeuk.com>: Sep 15 10:31AM +0100

On 15/09/2020 08:28, Juha Nieminen wrote:
> int of size 100", but since a "pointer to value" doubles as a "pointer
> to an array of values", that likewise doubles as a pointer to a
> two-dimensional array.
 
 
That's not helpful. Your example is a 'pointer to 2D array' in the same
way that:
 
int* p;
 
doubles as 'pointer to array'.
 
Although this is extremely common C idiom, it is NOT a pointer to 2D
array. An actual 'pointer to 2D array' would actually double, due to the
same idiom, as 'pointer to 3D array':
 
int (*p)[][100];
int i,j,k;
 
p[i][j][k];
 
What a language where you can have a discussion as to whether an array
has 2 dimensions or 3! At any point where you end up with a T* type, you
can optionally turn that into a gratuituous array dimension, where the
value is set up for such an array or not.
 
> array, where both dimensions are specified:
 
> int (&r)[200][100];
 
> The semantics of this would require another essay to explain.
 
(My language only has such references for parameters. There, assuming
the most likely meaning of that C++ type, the same thing would be
declared as:
 
[200,100]int &r
 
which means that the type is really 'ref[200,100]int r', but the
necessary & and deref operations are done transparently.)
Ben Bacarisse <ben.usenet@bsb.me.uk>: Sep 15 11:06AM +0100


> The correct syntax for "pointer to a two-dimensional array of int, where
> the second dimension is 100" is:
 
> int (*p)[100];
 
No, that's not the type you asked for. It is usual to /access/ an array
of int using an int pointer, but a pointer to an array of int is another
thing altogether. Likewise, you can use the above to access the
elements of an array whose element type is int[100], but it is not a
pointer to 2D array.
 
> int of size 100", but since a "pointer to value" doubles as a "pointer
> to an array of values", that likewise doubles as a pointer to a
> two-dimensional array.
 
Not at all. A pointer to int (int *) points to an int -- one single
int. That may be a lone int, and int as the start of an array or an int
in the middle of an array, but it is not a pointer to an array of int.
 
> that points to int[100] and have it point to such an array.
 
> It's perhaps understandable, but telling, that even many experienced
> C (and C++) programmers don't know this, even after decades.
 
I think you have just added to the general confusion. Don't call a
pointer to int a pointer to an array of int, call it a pointer /into/ an
array, if you must, or you can just stick with what it is: a pointer to
int.
 
Likewise, a pointer to an array of 100 ints is not a pointer to 2D array
if ints, it is a pointer /into/ (i.e. to one member of) such an array.
 
--
Ben.
olcott <NoOne@NoWhere.com>: Sep 15 08:45AM -0500

On 9/15/2020 4:25 AM, Paavo Helde wrote:
 
> Foe example, std::string is much more easier to use correctly than
> strcat(). By using std::vector and std::array one can easily avoid all
> syntax and behavior complexities of raw arrays. The list goes on.
 
You totally get it !!!
 
--
Copyright 2020 Pete Olcott
olcott <NoOne@NoWhere.com>: Sep 15 08:46AM -0500

On 9/15/2020 5:06 AM, Ben Bacarisse wrote:
> int.
 
> Likewise, a pointer to an array of 100 ints is not a pointer to 2D array
> if ints, it is a pointer /into/ (i.e. to one member of) such an array.
 
I don't see the difference.
 
--
Copyright 2020 Pete Olcott
olcott <NoOne@NoWhere.com>: Sep 15 09:10AM -0500

On 9/15/2020 9:03 AM, Bonita Montero wrote:
 
> Not automatically. You have to write "vecA = move( vecB );"
> instead of "vecA = vecB;". That's trivial, but you have to
> know how to use it.
 
I don't rememeber ever needing to do this, I simply used the default
assignment operator().
 
--
Copyright 2020 Pete Olcott
olcott <NoOne@NoWhere.com>: Sep 15 09:16AM -0500

On 9/15/2020 8:46 AM, olcott wrote:
 
>> Likewise, a pointer to an array of 100 ints is not a pointer to 2D array
>> if ints, it is a pointer /into/ (i.e. to one member of) such an array.
 
> I don't see the difference.
 
Whenever I need a two-dimensional array I simply use this one:
// comp.lang.c++ message from Gianni Mariani Nov 24, 2005 at 9:21 pm
// http://groups.google.com/group/comp.lang.c++/msg/a9092f0f6c9bf13a
 
 
--
Copyright 2020 Pete Olcott
Bonita Montero <Bonita.Montero@gmail.com>: Sep 15 04:18PM +0200

>> know how to use it.
 
> I don't rememeber ever needing to do this, I simply used the default
> assignment operator().
 
This makes move-semantics only if vecB is a temporary.
Bart <bc@freeuk.com>: Sep 15 03:39PM +0100

On 15/09/2020 14:46, olcott wrote:
 
>> Likewise, a pointer to an array of 100 ints is not a pointer to 2D array
>> if ints, it is a pointer /into/ (i.e. to one member of) such an array.
 
> I don't see the difference.
 
If P points to an array of 100 ints, and Q points to the first element
of that array, what do you think each of these does:
 
++P;
++Q;
 
?
 
Hint: the difference matters.
Melzzzzz <Melzzzzz@zzzzz.com>: Sep 15 01:34AM


>> You have pmaxsd instrunction and I guess intrinsic for x86. No need for
>> this abomination and much more efficient.
 
> I did try to ask for C++ not assembler nor compiler builtins.
 
Then don't bother.
 
 
--
current job title: senior software engineer
skills: c++,c,rust,go,nim,haskell...
 
press any key to continue or any other to quit...
U ničemu ja ne uživam kao u svom statusu INVALIDA -- Zli Zec
Svi smo svedoci - oko 3 godine intenzivne propagande je dovoljno da jedan narod poludi -- Zli Zec
Na divljem zapadu i nije bilo tako puno nasilja, upravo zato jer su svi
bili naoruzani. -- Mladen Gogala
Bonita Montero <Bonita.Montero@gmail.com>: Sep 15 07:04AM +0200

> return choose[ b > a ]( a, b );
> }
> Compiles cleanly as both C{90,99,11} and C++{98,03,11,14,17}.
 
It needs a good compiler to optimize away these stupid
function-calls. Otherwise this code would be rather slow.
"Öö Tiib" <ootiib@hot.ee>: Sep 14 11:55PM -0700

On Tuesday, 15 September 2020 08:04:52 UTC+3, Bonita Montero wrote:
> > Compiles cleanly as both C{90,99,11} and C++{98,03,11,14,17}.
 
> It needs a good compiler to optimize away these stupid
> function-calls. Otherwise this code would be rather slow.
 
Yes, but it usually is not generating branches like I did ask.
 
Your trick, Bonita, is also good as majority of compilers
(and all popular) optimize that ?: away on targets.
For example on RISC-V:
 
bonita_max(int, int):
slt a5,a0,a1
subw a5,zero,a5
xor a1,a0,a1
and a1,a1,a5
xor a0,a0,a1
sext.w a0,a0
ret
 
tim_max(int, int):
sgt a5,a1,a0
slli a4,a5,3
lui a5,%hi(.LANCHOR0)
addi a5,a5,%lo(.LANCHOR0)
add a5,a5,a4
ld t1,0(a5)
jr t1
 
Interesting trick, jumping to address of called function and letting
it to return for you. Haven't profiled, my own convoluted code most
likely performs worst everywhere. :D
Bonita Montero <Bonita.Montero@gmail.com>: Sep 15 08:58AM +0200

> Interesting trick, jumping to address of called function and letting
> it to return for you. Haven't profiled, my own convoluted code most
> likely performs worst everywhere. :D
 
Wouldn't work with shadow-stacks which will be introduced
by Intel's control-flow-enforcement technology.
"Öö Tiib" <ootiib@hot.ee>: Sep 15 12:06AM -0700

On Tuesday, 15 September 2020 09:58:56 UTC+3, Bonita Montero wrote:
> > likely performs worst everywhere. :D
 
> Wouldn't work with shadow-stacks which will be introduced
> by Intel's control-flow-enforcement technology.
 
Doesn't matter as I mostly try to make Intel's competitors to look
good. By my philosophy world without monopolies is better for
everybody, Intel included.
Juha Nieminen <nospam@thanks.invalid>: Sep 15 07:32AM

> return a & mask | b & ~mask;
> }
 
> The ternary operation is often substituted by a "sbb regx, regx".
 
Where's the carry flag coming from?
 
(Btw, if you use #include <cstdint> you have to use std::int32_t,
not int32_t. I don't think that the standard guarantees that
<cstdint> will dump the names into the global namespace.
You have to use <stdint.h> for that.)
Bonita Montero <Bonita.Montero@gmail.com>: Sep 15 09:51AM +0200

>> }
>> The ternary operation is often substituted by a "sbb regx, regx".
 
> Where's the carry flag coming from?
 
"cmp regA, regB; sbb regMask, regMask"
Tim Rentsch <tr.17687@z991.linuxsc.com>: Sep 15 01:45AM -0700


>> It needs a good compiler to optimize away these stupid
>> function-calls. Otherwise this code would be rather slow.
 
> Yes, but it usually is not generating branches like I did ask.
 
Do you mind if I ask what is motivating the original question?
Or what the criteria are for a successful answer? The answer
I gave was somewhat tongue-in-cheek since even though it was
not using if() or ?: I was pretty sure it was not what you
were looking for.
 
I do have a solution that uses only common operators, and no
function call trickery (and of course no library functions), and
thus likely satisfies what you would call "raw" C++, but before I
post something more I want to be sure I know what it is you're
really looking for.
 
> jr t1
 
> Interesting trick, jumping to address of called function and letting
> it to return for you.
 
As tail calls go it's one of the simplest, which makes it one
of the easiest optimizations to do. I'm surprised in a way
that compilers don't do this particular optimization even
at level -O0.
"Öö Tiib" <ootiib@hot.ee>: Sep 15 06:03AM -0700

On Tuesday, 15 September 2020 11:45:57 UTC+3, Tim Rentsch wrote:
> I gave was somewhat tongue-in-cheek since even though it was
> not using if() or ?: I was pretty sure it was not what you
> were looking for.
 
I understood it was tongue-in cheek but it was still interesting
to look what compilers do from it at different platforms, thanks.
Actually weather was bad at weekend and so I was reading various
interesting computation and vectorising algorithm papers (doing
lot of same calculation in parallel). So that silly idea just
randomly popped into mind.
 
> of the easiest optimizations to do. I'm surprised in a way
> that compilers don't do this particular optimization even
> at level -O0.
 
Yes, these usually do with ordinary call. Here was (kind of
calculated) function pointer, may be that somehow affects it.
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Sep 15 01:58AM +0100

On Mon, 14 Sep 2020 00:16:32 -0400
> such array, then 4.3 applies, and the behavior is undefined.
> If C++2020 handles this issue better than C does, the different wording
> that makes that true must lie in some other part of the document.
 
I don't agree as a matter of English that C++20's [expr.add]/4 has the
same effect as §6.5.6/8. Furthermore the C standard states of the C
allocation functions and arrays that "The pointer returned if the
allocation succeeds is suitably aligned so that it may be assigned to a
pointer to any type of object with a fundamental alignment requirement
and then used to access such an object or an array of such objects in
the space allocated (until the space is explicitly deallocated)". You
seem to be arguing that the only means of constructing such an array is
via memcpy() or memmove(). If so I disagree: things must be read in
context. Let's agree to disagree because I don't think this is worth
spending further time on.
 
> array, without either of those two special cases applying. Therefore, a
> strict reading of the rules of pointer arithmetic gives access to any
> element of such an array other than the first element undefined behavior.
 
See above on arrays. The wording isn't ideal but it just about does it
I think.
 
 
> X *p = (X*)std::malloc(n*sizeof(struct X));
 
> would implicitly create an n-element array. I certainly wouldn't object
> to that being the case, but it doesn't clearly say so.
 
On considering the standard further I think it does clearly say so once
you try to iterate over this memory by pointer arithmetic.
[basic.types]/9 says that all array types array types are
implicit-lifetime types. An implicit-lifetime type will automatically
be constructed in malloc'ed memory if doing so would result in the
program having defined behavior ([intro.object]/10 and [c.malloc]/4).
If you iterate a pointer over malloc'ed memory as if it were an array
then it becomes an array because that is necessary to give the program
defined behaviour. (As it happens that is also indisputably the
intention of p0593r6, and I think that intention has been achieved.)
 
> undefined behavior, so this represents a defect in the standard, but I
> don't see any way to justify saying that there's an array that can be
> used to give meaning to such pointer arithmetic.
 
See above
 
> > array types are now implicit-lifetime types. I will need to consider
> > revised [intro.object]/10 further on this.
 
> Could you explain what that section says that makes you feel that way?
 
See above
 
> defined for 1 + &i, or have I missed something that defines it? I've
> just started reading this version of the standard - I wouldn't be
> surprised if I missed something.
 
I have no idea what you mean. At any rate, I am pretty certain that
you have misunderstood what I meant. If you want to continue (and I am
not necessarily suggesting that you should, but do so if you would
like), can you start from the beginning again without reference to the
text of prior postings in a concise form.
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Sep 15 10:24AM +0100

On Tue, 15 Sep 2020 01:58:55 +0100
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote:
[snip]
> then it becomes an array because that is necessary to give the program
> defined behaviour. (As it happens that is also indisputably the
> intention of p0593r6, and I think that intention has been achieved.)
 
By the way, please note that on my reading an array can come into
existence as an implicit-lifetime type without the array elements
coming into existence. That would have to be the case where the array
elements are not themselves implicit-lifetime types: they would need to
be constructed element by element by placement new (not placement
new[], which cannot reliably be used). The fact that there is an array
in existence means that you can iterate a pointer over the memory in
order to use placement new.
 
Of course, if the array elements are of implicit-lifetime type, then
they can be constructed by memcpy()/memmove() or by assignment.
Placement new is not necessary.
 
> not necessarily suggesting that you should, but do so if you would
> like), can you start from the beginning again without reference to the
> text of prior postings in a concise form.
 
OK, I now see your point. You are pointing out that the text which says
that "For purposes of pointer arithmetic and comparison, an object that
is not an array element whose address is taken in this way is
considered to belong to an array with one element of type T" has been
removed in C++20. I hadn't noticed that.
 
That created a further problem with N4849. However, I don't think that
is an issue with N4860 because of the implied existence of an array.
Juha Nieminen <nospam@thanks.invalid>: Sep 15 07:11AM

> Where did you get the idea that CPUs like consecutive memory accesses?
 
Pretty much in any documentation you care to search. If you really want the
nitty-gritty details, read eg. Intel's own optimization documentation.
 
The CPU has liked consecutive memory accesses for pretty much as long as
they have been using caches (which would be somewhere around the 80486
or the Pentium).
 
When you read a value from RAM for the first time, the cache logic will not
just load exactly the amount of bytes that you read, but a block of RAM
from around that location (I don't remember now how large it was, but it
might have been something like 64 bytes or the like).
 
This means that if you read a value from a particular memory location and
it's not already in L1 cache, a block of memory will be read into cache at
once, and if your next access is within that block, it will be
extraordinarily fast, because the value will already be in L1 cache.
 
You can test this yourself (I have). For example, dynamically allocate
like a 1-gigabyte array, and read its bytes in its entirety but jumping
in steps of, like 256 bytes (ie. you first read the byte at offset 0,
then the one at offset 256, then the one at offset 512 and so on. When
you get to the end of the array, do the same but at offset 1, 257, 513
and so on. Keep doing this until you have read every single byte.)
To stop the compiler from optimizing your reading loop away, calculate
eg. the sum of all the bytes and print it.
 
Then change the program to do the same, but just read the bytes in the
array consecutively from beginning to end.
 
The difference in speed is *staggering*.
 
The reason for this is that in the first case every single read will cause
a complete cache miss, and is extremely slow. In the second case there will
be a cache miss only every 64th read or such.
 
>>largely work against this, causing even more performance hits.
 
> Again, modern processors handle this rather well using techniques
> such as branch target buffers and return stack caches.
 
Clearly you haven't actually tested the difference in speed that handling
an array of values directly has compared to making a function call that
handles one member variable at a time.
 
>>examples of this.
 
> Please do. Also note the relatively small number of code sequences that
> are actually amenable to autovectorization.
 
I'm a bit busy at the moment so I don't really have the time to write code,
but I noticed this quite clearly in one hobby project of mine.
 
I had a class that had a large std::vector as a member (containing instances
of a struct with a few integers inside), and the class had a member function
that returned a vector element and another that could be used to set it.
Due to technical details it wasn't feasible to make these functions inline,
so they were implemented in their own compilation unit (which caused an
actual function call to be done when calling either one).
 
At some point I was wondering why doing an operation to the entire vector
from the outside using these two functions was so slow. If I added a third
function that simply returned a pointer to the beginning of the vector
and did the same operation directly to the data, without any member
function calls, it was significantly faster, like 5 times faster or so.
 
This made little sense. A slight slowdown, like a few percents, would have
made sense because of the function call, but given that the CPU does
predictive branching (especially on unconditional function calls), a
function call shouldn't be any slower than if the code had been inlined.
Even if calling the function had put some values on the stack, it wouldn't
have made it 5 times slower.
 
However, when I examined the asm produced by the compiler, it all became
clear: The member function calls were hindering autovectorization, and
that's the reason why it was about 5 times slower.
 
You see, the version of the code that directly accessed the vector
through a pointer was being heavily autovectorized by the compiler.
However, the version that did so through the member function calls was
not. The compiler cannot autovectorize code that jumps somewhere else
to do something the compiler cannot see while compiling this particular
compilation unit.
 
(If the two member functions had been inline, it might have been a different
story. However, as said, in this particular case there were technical
reasons why they couldn't be inline, and needed to be in their own
compilation unit.)
Leo <usenet@gkbrk.com>: Sep 15 09:42AM +0300

On 9/15/20 1:23 AM, Nikki Locke wrote:
 
> If you know of a library which is not in the list, why not fill
> in the form at http://www.trumphurst.com/cpplibs/cppsub.php
 
> Maintainer: Nikki Locke - if you wish to contact me, please use the form on the website.
 
Won't this work better as an awesome list or a DuckDuckGo search with
"[topic name] c++"?
 
I think a whole webpage for it is overkill.
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: