Thursday, September 22, 2022

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

Juha Nieminen <nospam@thanks.invalid>: Sep 22 06:16AM

> deprecated,??? he said on Twitter, expressing a personal opinion rather
> than a fresh Microsoft policy.???
 
> Wow ! Bold.
 
Is a "Rust religion" forming in the industry?
 
I remember when Java was supposed to be the Holy Grail and answer to
everything.
 
(Nowadays I get the feeling that Java survives solely because it's the
main/only programming language available for developing for Android
devices. Haven't exactly researched if it's the only possible, or just
the main one.)
David Brown <david.brown@hesbynett.no>: Sep 22 09:45AM +0200

On 22/09/2022 08:16, Juha Nieminen wrote:
>> than a fresh Microsoft policy.???
 
>> Wow ! Bold.
 
> Is a "Rust religion" forming in the industry?
 
Rust is, IMHO, far too immature to be the right choice for major
projects. It has some good points, but it needs to stabilise as a
language and get more and better tools before it can be an alternative
to C or C++ for a lot of work.
 
An alternative future solution is for the C++ folks to copy the useful
parts from Rust. Rust's memory safety (AFAIUI) relies partly on the use
of static analysis tools run on the code. There are a variety of static
and dynamic analysis tools available for C++, but they are often tightly
to specific compilers or expensive third-party tools. If a common
standard could be formed and integrated with the standard library and
with open source tools, then C++ would be as "safe" as Rust for those
that chose to use these tools.
 
> main/only programming language available for developing for Android
> devices. Haven't exactly researched if it's the only possible, or just
> the main one.)
 
The preferred language for Android is now Kotlin (which runs on the same
JVM virtual machine).
Juha Nieminen <nospam@thanks.invalid>: Sep 22 09:41AM

> security and reliability, the industry should declare those languages as
> deprecated,??? he said on Twitter, expressing a personal opinion rather
> than a fresh Microsoft policy.???
 
I took a cursory look at Rust, and it really seems to embrace the
"brevity-over-clarity" style of programming.
 
What I call "brevity-over-clarity style of programming" is something
I have opposed and learned to hate over the years. For some reason it
appears to be what a good majority of beginner programmers naturally
gravitate towards, and some of them never unlearn. It's the general
use of names that are as short as possible, usually with complete
disregard to readability and understandability. (For example,
using the variable name "err" instead of "errorCode". Or "ret"
instead of "returnValue". Or "i" instead of "index". Sometimes the
word may be spelled-out rather than contracted, but in itself is
very non-descript without any further qualifiers. For example a
function named "convert()". Convert what? And into what?)
 
It appears that functions in Rust are declared with a keyword. What is
this keyword? Perhaps "function"? Of course not. It's "fn". Because
why wouldn't it be? (It couldn't get much shorter than that, unless
you want it to be just "f". Which actually wouldn't even surprise me.)
 
In order to declare a varible as mutable you have to specify that with
a keyword. And what is this keyword? Perhaps "mutable" (like in C++)?
Of course not. It's "mut", because why wouldn't it be? Who cares about
readability? You are saving typing a whopping 4 characters!
 
Obviously type names are as short as possible, such as "i32" and "u64".
 
Rust seems to also support a "using" keyword similar to the one used
in C++ and some other languages. Except that, obviously, "using" is
too long, so it's "use". (At least it is an entire English word.)
 
The standard library (or at least the little I skimmed over) seems to be
a bit of a mixed bag. Sometimes words might be spelled out in their
entirety, but not even nearly always. (For example, what possible reason
is there to call a function "gen_range" instead of "generate_range"? Is
that honestly too much to type?)
 
At least their string type is named "String". I wouldn't have been
surprised if it had been "Str". Must have been a lapsus.
Jens Stuckelberger <Jens_Stuckelberger@nowhere.net>: Sep 22 02:01PM

On Wed, 21 Sep 2022 14:04:43 -0500, Lynn McGuire wrote:
 
> security and reliability, the industry should declare those languages as
> deprecated," he said on Twitter, expressing a personal opinion rather
> than a fresh Microsoft policy."
 
A personal opinion is like an asshole: everybody's got one.
Ben Bacarisse <ben.usenet@bsb.me.uk>: Sep 22 04:46PM +0100

> word may be spelled-out rather than contracted, but in itself is
> very non-descript without any further qualifiers. For example a
> function named "convert()". Convert what? And into what?)
 
I've seen all of these in C, so why single out Rust? Does the language
itself somehow promote this style?
 
> this keyword? Perhaps "function"? Of course not. It's "fn". Because
> why wouldn't it be? (It couldn't get much shorter than that, unless
> you want it to be just "f". Which actually wouldn't even surprise me.)
 
Of course it could get shorter. In C it's nothing at all and you don't
complain about that degree of brevity!
 
> a keyword. And what is this keyword? Perhaps "mutable" (like in C++)?
> Of course not. It's "mut", because why wouldn't it be? Who cares about
> readability? You are saving typing a whopping 4 characters!
 
And C has int, struct, enum, char, const, extern... I think there is a
bit of a double standard here.
 
--
Ben.
Timothy Madden <terminatorul@gmail.com>: Sep 22 10:27AM

Hello
 
I am writing a class template for fixed capacity strings:
basic_static_string<SIZE, char, char_traits<char>>
(similar to the one in boost, but my company doesn't use newest version of
boost yet). I use C++14 at work, and the only data members of my class
template are:
std::size_t len;
char buffer[SIZE];
 
When I instantiate and construct a static string, like:
static_string<256u>("Delta")
I would like to only initialize the needed characters (characters 0 to 5)
and leave the other 251 characters uninitialized.
 
I would like my class to be constexpr, since there is no memory
allocation. However, somehow a constexpr constructor must use the member-
initializers to initialize the entire object.
 
Is there any way to use std::is_constant_evaluated() to completely
initialize the member array in a constexpr context, and partially
initialized it in a run-time context ?
 
This would be easy to do inside a function body with an if statement, but
in a member-initializer for a constructor it is not. I tried to use
is_constant_evaluated() in a template argument, but then it will always
return true. Why is the standard defined like that ? It makes
is_constant_evaluated() useless, when you actually need it...
 
--
Thank you,
Timothy Madden
Juha Nieminen <nospam@thanks.invalid>: Sep 22 11:14AM


> I would like my class to be constexpr, since there is no memory
> allocation. However, somehow a constexpr constructor must use the member-
> initializers to initialize the entire object.
 
You mean that you wouldn't want to have the initializer that I have marked
below with "// THIS"?
 
//---------------------------------------------------------------------
#include <cstddef>
 
template<std::size_t kCapacity>
class StaticString
{
char mData[kCapacity];
std::size_t mLength;
 
public:
template<std::size_t kStrLength>
constexpr StaticString(const char(&initStr)[kStrLength]):
mData{}, // THIS
mLength(kStrLength - 1)
{
for(std::size_t i = 0; i < kStrLength; ++i)
mData[i] = initStr[i];
}
 
constexpr const char* c_str() const { return mData; }
};
 
#include <iostream>
int main()
{
constexpr StaticString<256> str("hello world");
 
std::cout << "\"" << str.c_str() << "\"\n";
}
//---------------------------------------------------------------------
Timothy Madden <terminatorul@gmail.com>: Sep 22 02:32PM

On Thu, 22 Sep 2022 11:14:21 -0000 (UTC), Juha Nieminen wrote:
 
> Timothy Madden <terminatorul@gmail.com> wrote:
[...]
> }
 
> constexpr const char* c_str() const { return mData; }
> };
[...]
 
 
The member-initializer
: mData { }
zeroes-out 256 characters, when I only need to initialize 12 (for "hello
world").
 
This is the right thing to do in a constexpr context, but in a run-time
context I want to avoid it.
 
So my thought was to use std::is_constant_evaluated() somehow and only
apply the initializer if the function returns true. But the syntax of a
member-initializer doesn't seem to allow it.
Juha Nieminen <nospam@thanks.invalid>: Sep 22 12:41PM

> the promise that, by virtue of Rust's linear/affine type system, a
> component that compiles is guaranteed to be memory correct as long as
> the unsafe subset of Rust hasn't been used is quite a strong statement.
 
I happened to stumble across this, which seems to contradict that notion:
 
https://doc.rust-lang.org/book/ch15-06-reference-cycles.html
Paavo Helde <eesnimi@osa.pri.ee>: Sep 22 02:09PM +0300

I just realized it is legal to assign a temporary std::string to a
std::string_view
 
std::string foo();
 
void bar(std::string_view v) {
if (v.empty()) {
v = foo(); // BUG!
}
}
 
 
That seems way too dangerous to me. Is there something I could do to get
such things failing to compile? Would deriving from std::string_view and
declaring operator=(std::string&&) as deleted be a solution?
Juha Nieminen <nospam@thanks.invalid>: Sep 22 11:17AM

> I just realized it is legal to assign a temporary std::string to a
> std::string_view
 
I think there may be completely legitimate situations where you may want
to initialize a string_view with a temporary. Like:
 
foo(std::string_view(bar()));
Paavo Helde <eesnimi@osa.pri.ee>: Sep 22 03:19PM +0300

22.09.2022 14:17 Juha Nieminen kirjutas:
 
> I think there may be completely legitimate situations where you may want
> to initialize a string_view with a temporary. Like:
 
> foo(std::string_view(bar()));
 
Yes, I know. That's why my question was about assignment, not
initialization.
Juha Nieminen <nospam@thanks.invalid>: Sep 22 12:36PM


>> foo(std::string_view(bar()));
 
> Yes, I know. That's why my question was about assignment, not
> initialization.
 
std::string_view view;
foo(view = bar());
 
Isn't that completely normal code? :P
Ben Bacarisse <ben.usenet@bsb.me.uk>: Sep 22 12:39AM +0100

>> source.
 
> Yes, it is to copy a "C String" (Null Terminated) into a fixed width
> field.
 
Again, a quibble: not quite. A null will be respected, but there is no
need for the source to be a C string.
 
--
Ben.
Richard Damon <Richard@Damon-Family.org>: Sep 21 08:45PM -0400

On 9/21/22 7:39 PM, Ben Bacarisse wrote:
>> field.
 
> Again, a quibble: not quite. A null will be respected, but there is no
> need for the source to be a C string.
 
It may be able to do more, but I suspect the purpose it was designed was
for that.
Keith Thompson <Keith.S.Thompson+u@gmail.com>: Sep 21 05:55PM -0700

>> field.
 
> Again, a quibble: not quite. A null will be respected, but there is no
> need for the source to be a C string.
 
You're right (and I misstated it in a recent post). The standard (I'll
quote N1570 because I have it open) says:
 
The strncpy function copies not more than n characters (characters
that follow a null character are not copied) from the array pointed
to by s2 to the array pointed to by s1. If copying takes place
between objects that overlap, the behavior is undefined.
 
Which means that the source doesn't have to point to a C string.
 
The Linux Programmer's Manual man page incorrectly suggests that the
source has to be a pointer to a C string:
 
The strcpy() function copies the string pointed to by src,
including the terminating null byte ('\0'), to the buffer pointed
to by dest.
[...]
 
The strncpy() function is similar, except that at most n bytes of
src are copied.
 
The phrase "the string pointed to by src" in the description of strcpy
implies that the behavior is undefined if src doesn't point to a string.
The man page incorrectly implies that same wording applies to strncpy.
 
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
Working, but not speaking, for Philips
void Void(void) { Void(); } /* The recursive call of the void */
Keith Thompson <Keith.S.Thompson+u@gmail.com>: Sep 21 06:00PM -0700

>> no need for the source to be a C string.
 
> It may be able to do more, but I suspect the purpose it was designed
> was for that.
 
According to the standard's description, the source clearly does not
have to point to a C string -- and strncpy() would work perfectly well
to copy one fixed-sized buffer to another, which is a very plausible use
case if you're working with such a data structure.
 
char source[5] = "hello"; // no null terminator
char target[5];
strncpy(target, source, sizeof target);
 
Of course it also works if the source is a pointer to a C string, and
it's explicitly required to deal with the null terminator.
 
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
Working, but not speaking, for Philips
void Void(void) { Void(); } /* The recursive call of the void */
Bonita Montero <Bonita.Montero@gmail.com>: Sep 22 04:40AM +0200

Am 21.09.2022 um 23:14 schrieb David Brown:
 
>> Before C++11 volatile were partitially used where today you use atomics.
 
> If you used "volatile" thinking you got the effects of atomic access,
> you were wrong. ...
 
No, you could use volatile in an implementation-defined way and it did
partitially the same like atomic.
 
>>> be a lot more expensive than using volatile writes.
 
>> volatile writes can't be substituted with fences.
 
> A fence implies a memory barrier - ...
 
It's a different word for the same thing.
Juha Nieminen <nospam@thanks.invalid>: Sep 22 05:59AM

> If you use "volatile" when you mean "atomic", your code is wrong.
 
Yeah. Some programmers might have mistakenly thought that 'volatile'
means the same thing as "atomic", and thus "thread-safe". However,
programmers who know what they are doing also know that it isn't
anything like that.
 
I think that the exact semantics of 'volatile' might be largely
implementation-defined, but AFAIK in most if not all compilers
(especially gcc and clang) it effectively acts as an optimization
barrier. It tells the compiler "any access to this must never be
optimized away, nor moved to happen somewhere else in the code".
 
In other words, if you eg. write a loop where you read a
'volatile' ten times, then the compiler must generate code that
reads it ten times, inside that exact loop and nowhere else.
The compiler must not optimize it to one single read or
completely away (because it doesn't see anything changing it).
 
By far the most common (perhaps only) use of this is in embedded
programming, especially on very small processors, where things
like ports and CPU pins are mapped into RAM (which means that
reading the same memory location may give different values at
different times, and writing values there most definitely must
never be optimized away).
 
There's another useful use of 'volatile': In benchmarking.
Run the code that's to be benchmarked, and then assign the
return value to a volatile. This stops the compiler from
optimizing the whole thing away because it sees that the
result isn't being used for anything. (It might not have
optimized it away in the first place, but assigning the
result to a volatile makes sure of that.)
Juha Nieminen <nospam@thanks.invalid>: Sep 22 06:03AM


> I've always used memcpy. I've very seldom used any str* function
> other than strlen and once in a blue moon, strtok. I use snprintf
> in place of strcat, for instance.
 
memcpy requires you to know the length of the string in advance, which
is often not necessary, and would require traversing the string twice
in order to copy it. (Why traverse it twice? You can copy it while
traversing it for the first time!)
Juha Nieminen <nospam@thanks.invalid>: Sep 22 06:09AM

>> i.e. the destination is considered to be a fixed-width field but not the
>> source.
 
> Yes, it is to copy a "C String" (Null Terminated) into a fixed width field.
 
Then it should have been named something entirely different. As it is now
it gets extremely easily confused with strcpy(), as if it were a "safer"
variant of it, in the same was as strncat() is a "safer" variant of
strcat().
David Brown <david.brown@hesbynett.no>: Sep 22 08:56AM +0200

On 22/09/2022 04:40, Bonita Montero wrote:
>> you were wrong. ...
 
> No, you could use volatile in an implementation-defined way and it did
> partitially the same like atomic.
 
What do you think "volatile" does, as a qualifier for accesses?
 
What do you think "atomic" means - both in terms of what the C and C++
standards say, and what the term means more generally in programming?
 
There is a degree of overlap, but they are not the same thing.
 
 
>>> volatile writes can't be substituted with fences.
 
>> A fence implies a memory barrier - ...
 
> It's a different word for the same thing.
 
No, they are different things. Atomic fences are about synchronisation
between different threads - they ensure that threads running on
different cores see a consistent picture of the relevant data in memory.
Memory barriers are about ordering of the /local/ view of memory reads
and writes.
David Brown <david.brown@hesbynett.no>: Sep 22 09:32AM +0200

On 22/09/2022 07:59, Juha Nieminen wrote:
> (especially gcc and clang) it effectively acts as an optimization
> barrier. It tells the compiler "any access to this must never be
> optimized away, nor moved to happen somewhere else in the code".
 
Yes. You can think of "volatile" as telling the compiler "there are
things you don't know about that mean you can't apply as-if
optimisations here".
 
The implementation-defined nature of volatile accesses is unavoidable.
If you have, say, a volatile write to a uint32_t variable then most
32-bit or 64-bit systems will implement it as a single write. A 16-bit
system will have to break it into two writes. An original Alpha would
do a 64-bit read, a modify, then a 64-bit write. Most systems will
attempt a single write even if the address is unaligned, others might
break it down or the hardware might cause a trap on the unaligned
access. Accesses to bitfields have multiple possible implementations.
All in all, there's a lot that can't be specified in the standards.
 
Then there are read-write-modify accesses such as "v++;" or "v |=
0x0100;". Some architectures can do these atomically, others would need
bus locks and interrupt disabling to be atomic.
 
(As an interesting aside here, in the C standards up to C17 only
described "access to volatile objects". Only in C17 did it change to
"volatile accesses", defining what was meant by using a
pointer-to-volatile cast to access data that had not been defined as
volatile. I don't know what the C++ standards say there - but I believe
that, as for C, every compiler handles volatile accesses as you might
expect.)
 
 
> reads it ten times, inside that exact loop and nowhere else.
> The compiler must not optimize it to one single read or
> completely away (because it doesn't see anything changing it).
 
It can still unroll the loop. Similarly if you have :
 
volatile int v;
 
if (a) {
v = 1;
} else {
v = 2;
}
 
then the compiler can do:
 
int temp = a ? 1 : 2;
v = temp;
 
The run-time pattern of volatile accesses must match the "abstract
machine" exactly, but the pattern of the generated assembly need not.
 
> reading the same memory location may give different values at
> different times, and writing values there most definitely must
> never be optimized away).
 
Yes.
 
And for single-core processors (covering the great majority of
small-systems embedded programming), "volatile" is often sufficient in
many places where atomics would be needed in general. But you need to
be aware of its limitations here - it forms part of the solution, but
not necessarily all of it. (The gcc implementation of C11/C++11
atomics, at least in the gcc versions I have looked at, are dangerously
wrong for single core embedded systems.)
 
> result isn't being used for anything. (It might not have
> optimized it away in the first place, but assigning the
> result to a volatile makes sure of that.)
 
Yes, that kind of use is convenient. You also have to ensure that the
calculation depends on a volatile variable, not just that the result is
written to one. It gives you a more self-contained test than reading an
starting input from the command line and printf'ing the result.
David Brown <david.brown@hesbynett.no>: Sep 22 09:37AM +0200

On 22/09/2022 01:01, Keith Thompson wrote:
 
> If your code handles errors, always think about what
> that error handling will actually do.
 
That's good general advice for all programming - and something many
people don't consider deeply enough. Add to it that your error handling
code must be tested as well as the rest of your code. I've seen several
cases in practice where poor and untested error handling code turned a
glitch into a disaster.
 
 
(I agree with everything in the rest of your post - it's just that your
final sentence looked so good as a "tip of the day" !)
Bonita Montero <Bonita.Montero@gmail.com>: Sep 22 12:02PM +0200

Am 22.09.2022 um 08:56 schrieb David Brown:
 
> What do you think "volatile" does, as a qualifier for accesses?
 
A volatile read or write is usually at least the same as a read or
write through a memory_order relaxed. So there are some guarantees
you can rely on.
 
> different cores see a consistent picture of the relevant data in memory.
>  Memory barriers are about ordering of the /local/ view of memory reads
> and writes.
 
Fences and barriers mean the same. A fence or barrier makes that changes
from a foreign thread become visible to another thread or changes from a
thread become visible for other threads.
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: