Tuesday, April 27, 2021

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

Keith Thompson <Keith.S.Thompson+u@gmail.com>: Apr 26 04:57PM -0700

> Eli the Bearded <*@eli.users.panix.com> writes:
[...]
 
> *Lord* that one bit me hard early on. Having all struct members
> use a single namespace is a decision I still don't understand (I don't
> care how limited the machines running the compiler were).
 
As of the 1975 C manual, a "prefix.identifier" or "prefix->identifier"
expression *assumed* that the LHS was of the correct type. For "->",
it wasn't even required to be a pointer; it could be a pointer,
character, or integer.
 
K&R1 (1978) imposed the requirement for the LHS to be of the correct
struct or union type for "." or a pointer to the correct struct or union
type for "->".
 
If you ran into that problem, it must have been some very early code
and/or a very old compiler.
 
I've used a compiler (VAXC) that knew about the modern "+=" compound
assignment operators, but also accepted the older "=+" forms -- and
preferred them in ambiguous cases. That was in the late 1990s. That's
also a change that was made between 1975 and K&R1 in 1978. Fortunately,
though VAXC was available, we mostly used the more modern DECC.
 
[...]
 
--
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 */
Ben Bacarisse <ben.usenet@bsb.me.uk>: Apr 27 01:34AM +0100


>>The posted code can't be 50 years old. ed is 50 years old, but the
>>original was not written in C (there was no C in 1971).
 
> ed first appeared in V2. ed2.s and ed3.s
 
Oh. I found a man page dated 1971 in V1 Unix.
 
> https://minnie.tuhs.org/cgi-bin/utree.pl?file=V2/cmd/ed2.s
 
> ed.c first appeared in V6 (1975).
 
Yup. ed.c can't predate C!
 
 
--
Ben.
Kaz Kylheku <563-365-8930@kylheku.com>: Apr 27 12:46AM


> 1. It predated printf being called that. I think it was just using
> print(). And that used "-lS" in the Makefile.
 
> 2. Prototypes? Who needs em? ("#include <stdio.h>"? What, why?)
 
Version 7 Unix in 1979 had <stdio.h>, and fprintf and printf functions,
wrappers around an assembly language routine taking the stream as a
parameter. In version 5 (1974), printf was still a dedicated assembly
routine. It had the "f" in the name.
 
 
> struct {
> int p_xy; /* used to set/transfer entire plot */
> };
 
That was a feature of early C. Basically, no type checking. If you
wrote
 
ptr->member
 
then it would look up member in a global dictionary of *all* structure
members that have been declared in the translation unit, and not in the
*ptr type! And then it would just generate the machine code to access
the pointer relative to that offset.
 
This is part of the reason why Unix structure members have prefixes,
just like the convention you see in the code you found above.
 
E.g. "struct stat" has "st_mtime", "st_size" and so on.
 
That situation persists until today.
 
Another funny fact is that some early compilers used a static buffer for
returning structs instead of the stack. Oops, not safe w.r.t. signals or
threading.
 
> 5. Implicit in above: seems like there is the expectation that
> sizeof(int) == 2.
 
Newly written code today has assumptions like this. TOns of code written
for 16-bit systems (PC's with MS-DOS, for instance) was riddled with
sizeof(int) == 2 == 16 bits assumptions.
 
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Joe Pfeiffer <pfeiffer@cs.nmsu.edu>: Apr 26 09:42PM -0600

> type for "->".
 
> If you ran into that problem, it must have been some very early code
> and/or a very old compiler.
 
It wasn't an old compiler... at the time... it was roughly 1977 or so.
Juha Nieminen <nospam@thanks.invalid>: Apr 27 04:51AM

> 5. Implicit in above: seems like there is the expectation that
> sizeof(int) == 2.
 
That's actually a good point, which I didn't myself think of earlier.
 
Several arguments have been made in this thread that C is very "portable",
and that C code written in the 1970's still compiles and works just fine
(well, at least if you tell gcc/clang to use the C89 standard, hopefully).
 
However, the undetermined size of basic types (especially before
standardization) makes it more likely for C programs, especially ones
written back then, to be non-portable. Sure, even back when K&R first
"soft-standardized" the C language you shouldn't have assumed a certain
size for any basic type.
 
(I don't know if sizeof(char) was guaranteed to be 1 even since K&R,
but even then, and to this day, you can't really trust that it's actually
one 8-bit byte, only that the sizes of all other types are multiples of it.)
 
There were no fancy uint32_t and other such type aliases back then
(and not even in C89), so there wasn't really a sure way to have a basic
type of a particular size. (You can check if a basic type is of a given
size with #if, and try several of them to see if one of them is of
the desired size, and produce an #error if none of them are, but that's
as far as you could go. In fact, even today that's technically as far
as you can go, even with the possibly existing standard typedefs.)
 
For most code it's enough for basic types to have a minimum size, but this
isn't always the case, and it's easy to write code that assumes a particular
size for such a type and breaks if it actually isn't of that size.
 
I suppose the conclusion is that maybe C code written in the 70's does
compile and work today... but only if it was properly written. If it
wasn't properly written, it's perfectly possible it won't work today,
in a different architecture than it was originally written for
(for example because it assumes the wrong size for 'int'.)
David Brown <david.brown@hesbynett.no>: Apr 27 10:59AM +0200

On 26/04/2021 22:49, jacobnavia wrote:
>> to invent unrealistic reasons.
 
> Sure sure, I am desperate. But you ignored the problem with templates...
> and many others I mentioned
 
I didn't ignore them, I just didn't mention them. There /are/ backwards
incompatible changes between C++03 and C++11 (and later versions). Some
of these will be in language or library features that might well occur
in real code. It would be perfectly reasonable to point out these
differences - especially if you can give examples or references to real
cases.
 
What is /unreasonable/ and shows desperation (perhaps people moving to
C++ is bad for your business) is listing every little point you can
think of, regardless of realism. It makes a mockery of your /real/ points.
scott@slp53.sl.home (Scott Lurndal): Apr 27 02:28PM


>Things I found tricky about the code:
 
>1. It predated printf being called that. I think it was just using
> print(). And that used "-lS" in the Makefile.
 
printf was printf from day one. Sounds like 'print' was provided
by an implementation library that wasn't included with the tar file you resurrected.
 
 
>2. Prototypes? Who needs em? ("#include <stdio.h>"? What, why?)
 
Prototypes were not used at the time, argument types were
'flexible', the programmer was expected to do the right thing.
 
> char p_x;
> char p_y;
> } p_plot[LIM_PLOTS];
 
Now this isn't a union. p_x and p_y occupy unique memory
locations.
 
But member names (MoS - Member of Structure) were top-level
symbol table names, so any member from any struct could be
used with any other struct, so:
a
 
> struct {
> int p_xy; /* used to set/transfer entire plot */
> };
 
p_xy has offset zero from the start of the structure; when software
uses p_xy (e.g. structpointer->p_xy) it has offset zero which allows
16-bit accesses to the two 8-bit fields in the p_plot struct.
 
 
>5. Implicit in above: seems like there is the expectation that
> sizeof(int) == 2.
 
Yes, for that particular application, it was assumed that
sizeof(int) == 2 * sizeof(char). Which was the case on the
PDP-11.
Juha Nieminen <nospam@thanks.invalid>: Apr 27 03:11PM

>>2. Prototypes? Who needs em? ("#include <stdio.h>"? What, why?)
 
> Prototypes were not used at the time, argument types were
> 'flexible', the programmer was expected to do the right thing.
 
I love the example given in the original 1978 The C Programming
Language, which demonstrates how non-int-returning standard
library functions ought to be used:
 
FILE *fopen(), *in;
in = fopen("name", "r");
 
Apparently back in those days <stdio.h> couldn't be expected
to declare the fopen function.
Joe Pfeiffer <pfeiffer@cs.nmsu.edu>: Apr 27 10:03AM -0600

> written back then, to be non-portable. Sure, even back when K&R first
> "soft-standardized" the C language you shouldn't have assumed a certain
> size for any basic type.
 
Everybody "knew" longs were 32 bits, shorts and ints were both 16 bits,
chars were 8 bits and we coded under that assumption. Lots and lots of
code broke when we moved from 16 bit to 32 bit machines and the size of
an int changed as a result.
 
<snip>
 
> wasn't properly written, it's perfectly possible it won't work today,
> in a different architecture than it was originally written for
> (for example because it assumes the wrong size for 'int'.)
 
Correct (as those of us who are old enough learned in the 80s!).
Keith Thompson <Keith.S.Thompson+u@gmail.com>: Apr 27 09:20AM -0700

> chars were 8 bits and we coded under that assumption. Lots and lots of
> code broke when we moved from 16 bit to 32 bit machines and the size of
> an int changed as a result.
 
Everybody who programmed on PDP-11s knew that. There were other
configurations at least as early as K&R1, 1978. But most programmers at
the time probably wouldn't have worked on more than one system.
 
 
--
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 */
jacobnavia <jacob@jacob.remcomp.fr>: Apr 27 06:31PM +0200

Le 25/04/2021 à 15:19, Öö Tiib a écrit :
> I do not see that point ... What companies want to reduce C++ by using more COBOL,
> FORTRAN, Ada, D or C? Can anyone point at such companies? Any cite?
 
Look. C is the most popular programming language, according to the TIOBE
index
 
https://www.tiobe.com/tiobe-index/
 
SOME companies must be using it, don't you think so?
 
C++ comes 4th.
scott@slp53.sl.home (Scott Lurndal): Apr 27 04:34PM

>chars were 8 bits and we coded under that assumption. Lots and lots of
>code broke when we moved from 16 bit to 32 bit machines and the size of
>an int changed as a result.
 
And, unfortunately, the types often were not abstracted behind
a typedef. The process identifer, group identifier and user
identifers were all 16-bit as well. This made for a painful
conversion to 32-bit PID/GID/UID in SVR4 (accompanied by abstract
types pid_t, gid_t, uid_t which, when used correctly, mean that
applications only needed a simple recompile to handle a change in width).
Joe Pfeiffer <pfeiffer@cs.nmsu.edu>: Apr 27 10:55AM -0600

> conversion to 32-bit PID/GID/UID in SVR4 (accompanied by abstract
> types pid_t, gid_t, uid_t which, when used correctly, mean that
> applications only needed a simple recompile to handle a change in width).
 
There were no typedefs back then (at least I don't remember them that
far back, and they don't appear in either Ritchie's reference manual nor
Kernighan's tutorial).
Kaz Kylheku <563-365-8930@kylheku.com>: Apr 27 05:11PM

>>an int changed as a result.
 
> And, unfortunately, the types often were not abstracted behind
> a typedef.
 
Equally unfortunately, types were often abstracted behind bad typedefs.
 
:)
 
 
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Juha Nieminen <nospam@thanks.invalid>: Apr 27 12:07PM

Recently I posted a controversial thread where I recounted my recent
experience trying to tutor a beginner programmer who wanted to create
a small program that would read a file and allow the user to edit it,
like a simple text editor, and how frustrating it was trying to do
this in C due to the complexities of its memory management.
 
Because such accusations about C is heresy, I thought I would redeem
myself and cleanse my sins by commenting a bit on the flipside, based
on actual examples out there that I have had to deal with.
 
While trying to tutor that beginner, I was constantly thinking how
much easier the task would have been in C++, using std::string
(and std::vector, etc).
 
Indeed, std::string makes such things so much easier and simpler.
On the flipside, perhaps it makes things a bit *too* easy, as it
induces people to create inefficient code with it, perhaps due to
ignorance or laziness.
 
For example, I am currently dealing with C++ code that does a lot of
things like:
 
if(str1.substr(n, 5) == str2)
 
Mind you, this is in commercial production code done by a relatively
experienced C++ programmer, not some noob.
 
I'm constantly thinking like "sheesh, there's already a function that
does that exact comparison in-place, without creating any temporary
copies, avoiding a useless allocation, copying of data and deallocation."
Namely:
 
if(str1.compare(n, 5, str2) == 0)
 
In this particular case it's not like the more efficient version is more
complicated to write or use, or would require several lines of code,
or something.
 
There are lots and lots of other examples, related to std::string
(and std::wstring), such as using it for fixed-sized strings in situations
where a small inbuilt char array would suffice, always taking a std::string
as parameter when a const char* would suffice (or could well be
provided as a more efficient alternative) and so on and so forth.
 
std::string is an awesome tool that makes one's life a thousand times
easier, but any competent C++ programmer should learn how to use it
*efficiently*, not just lazily. Know your tools. Use them efficiently.
Paavo Helde <myfirstname@osa.pri.ee>: Apr 27 05:57PM +0300

27.04.2021 15:07 Juha Nieminen kirjutas:
> copies, avoiding a useless allocation, copying of data and deallocation."
> Namely:
 
> if(str1.compare(n, 5, str2) == 0)
 
I have wondered about this exact thing myself. So I now made a little
test to check if and how much the slowdown actually is.
 
It looks like with small strings so that SSO gets used, substr()
comparing is ca 2x slower than compare(), and with large strings
(needing a dynamic allocation) substr() comparing is ca 10x slower than
compare().
 
OTOH, a single compare() is only something like 4 ns, so if a program
makes e.g. a million of such comparisons, then with large substrings it
would slow down from 0.004 s to 0.04 s. I bet many people won't be
overly concerned about this.
James Lothian <jameslothian1@gmail.com>: Apr 27 12:28PM +0100

Andrey Tarasevich wrote:
>   {
>     typedef Unit<L1 + R1, L + R...> Result;
>   };
 
Thanks for this -- it's less horrible than the workaround I've been using.
 
James
TDH1978 <thedeerhunter1978@movie.uni>: Apr 26 09:10PM -0400

I have a database-related class (MyDB) where I would like to overload
the [] operator, so that the class looks and behaves like a map.
 
MyDB mydb; // key-value pairs, like a map
 
//
// read from database
//
X x = mydb[y];
 
//
// write to database (using proxy object)
//
mydb[y] = x;
 
 
Does anyone know how I can do this?
 
I tried using a proxy class for the 'write' operation, but when it came
time to print a database entry, the compiler would complain that it
does not know how to print the proxy object, when in fact it should be
printing 'x' from the 'read' operation:
 
cout << mydb[y] << endl; // compiler error; tries to print proxy
object and not 'x'.
 
 
I was hoping that the last 'cout' statement would be the same as:
 
cout << x << endl;
 
But the compiler had other ideas.
 
=================================
 
Below is some pseudo-code (does not compile due to incompleteness).
 
//
// Proxy for the MyDB class
//
class MyDBProxy_
{
public:
 
MyDBProxy_(MyDB* owner) : mydb_(owner) {}
 
void set_key(const Y& y)
{
y_ = y;
}
 
void operator=(const X& x)
{
mydb_->store_value_(y_, x); // internal database operation
}
 
private:
MyDB* mydb_;
Y y_;
};
 
 
//
// The actual database class
//
class MyDB
{
friend class MyDBProxy_;
 
public:
 
MyDB() : proxy_(this)
{
// internal databse construction
}
 
//
// read operation: X x = mydb[y];
//
X operator[](const Y& y) const
{
return fetch_value_(y); // internal database operation
}
 
//
// write operation: mydb[y] = x;
//
MyDBProxy_& operator[](const Y& y)
{
proxy_.set_key(y);
 
return proxy_;
}
 
// definition of fetch_value_ here...
// definition of store_value_ here...
 
private:
 
MyDBProxy_ proxy_; // proxy object
};
Paavo Helde <myfirstname@osa.pri.ee>: Apr 27 02:02PM +0300

27.04.2021 04:10 TDH1978 kirjutas:
> printing 'x' from the 'read' operation:
 
>   cout << mydb[y] << endl;  // compiler error; tries to print proxy
> object and not 'x'.
 
You need to add a streaming operator for your proxy. Something like:
 
std::ostream& operator<<(std::ostream& os, const Proxy& proxy) {
os << proxy.get_value();
return os;
}
Cholo Lennon <chololennon@hotmail.com>: Apr 26 09:35PM -0300


> I'm confused why you think the 2nd example is clearer or neater, requiring
> as it does a 2nd class instead of handling everything in 1. But everyone has a
> prefered style I suppose, its just a matter of taste.
 
I am just showing different use cases, I am not saying than the 1st
example is better than the 2nd one (or vice versa), but the latter is
very common, for example, in GUI applications... your Window/Dialog
class handles the events from the contained widgets. You can use free
functions as event handlers, but member functions has the context of the
parent class (of course, std::bind is very powerful so you can add the
context to your free functions if you want), i.e:
 
 
class MyWindow: public Window {
Button okCancel;
EditBox name;
EditBox address;
 
SomeWindowContext context;
 
void onClick(Button& button) {
if (button.isCancel()) {
...
}
else {
...
}
}
 
void onFocus(EditBox& editBox) {
// Use window's context here
}
 
void onLostFocus(EditBox& editBox) {
// Use window's context here
}
 
void initWidgets() {
...
okCancel.onClick = std::bind(&MyWindows::onClick, this, _1);
 
// For simplicity, callbacks are reused for similar widgets
name.onFocus = std::bind(&MyWindows::onFocus, this, _1);
name.onLostFocus = std::bind(&MyWindows::onLostFocus, this, _1);
 
address.onFocus = std::bind(&MyWindows::onFocus, this, _1);
address.onLostFocus = std::bind(&MyWindows::onLostFocus, this, _1);
...
}
 
public:
MyWindow(...): ... {
initWidgets();
}
 
void show() {
...
}
 
};
 
 
C# uses the same scheme via delegates (the .Net std::function
counterpart). Java used to have a similar approach in Swing, but
anonymous classes were used to redirect a widget event to the parent
class callback. In modern C++ we can use a lambda to code the event or
to redirect it to the parent class. There are a lot of approaches, all
of them with pros and cons.
 
Usually, the initWidget member function (initializeComponent/jbInit in
C#/Java) are managed by the GUI editor/IDE, so the event connection (as
well as the widget configuration) is automatic. In the old days of MFC,
a message map built with macros was the initWidget equivalence. The map
was managed by the Class Wizard (*) tool.
 
I use the past tense to talk about MFC because my last contact with it
was 16 years ago :-O but I think Class Wizard (and the message map)
still exist.
 
 
--
Cholo Lennon
Bs.As.
ARG
Bonita Montero <Bonita.Montero@gmail.com>: Apr 27 05:32AM +0200

>        name.onLostFocus = std::bind(&MyWindows::onLostFocus, this, _1);
>        address.onFocus = std::bind(&MyWindows::onFocus, this, _1);
>        address.onLostFocus = std::bind(&MyWindows::onLostFocus, this, _1);
 
You would never use bind()-objects themselfes as callback-types because
the types are variable depending on the parameters. Usually you would
encapsulate them into a function-object which gives you the advantage,
that you can have any types of paramters with them, f.e.:
name.onFocus = function<void(param_type)>( bind(
&MyWindows::onFocus, this, _1 ) );
Cholo Lennon <chololennon@hotmail.com>: Apr 27 12:46AM -0300

On 4/27/21 12:32 AM, Bonita Montero wrote:
> that you can have any types of paramters with them, f.e.:
>          name.onFocus = function<void(param_type)>( bind(
> &MyWindows::onFocus, this, _1 ) );
 
But for my previous post it should be clear that onFocus/onLostFocus/etc
are declared as std::function. For the sake of simplicity I didn't add
the declaration/definition of these properties (because they belong to
classes, Button and Editbox, whose code I didn't show)
 
--
Cholo Lennon
Bs.As.
ARG
MrSpook_1xajf_a1kl@f5mda.co.uk: Apr 27 07:37AM

On Mon, 26 Apr 2021 21:35:13 -0300
>functions as event handlers, but member functions has the context of the
>parent class (of course, std::bind is very powerful so you can add the
>context to your free functions if you want), i.e:
 
Perhaps I'm just biased, but IMO if the answer is using std::bind() to link
an object method to a function pointer then you're asking the wrong
question. In simple code I'm sure it wouldn't be an issue, but in a complex
project that may be hacked about by dozens of people over the years the chances
the object may be deleted leaving a dangling function pointer elsewhere in
the code grows much higher.
 
>well as the widget configuration) is automatic. In the old days of MFC,
>a message map built with macros was the initWidget equivalence. The map
>was managed by the Class Wizard (*) tool.
 
A much safer and simpler method on an event is simply to call a callback
with the widget id. Event driven GUI code isn't speed critical so if safety
and simplicity costs a few extra cpu cycles then its a trade worth making.
Juha Nieminen <nospam@thanks.invalid>: Apr 27 04:37AM

> refactoring anyway. Are you speaking of a real-life example? Dozens of
> different struct types that all include one another and none of which
> (originally) have dynamically allocated members?
 
But you yourself said that you don't automatically add manual constructors
and destructors to every struct you write, just in case.
 
I don't even understand what you mean by "well-designed code" then.
Is "well-designed" C code one where every struct has a constructor and
destructor function (that you always call for every single instance)?
Or is it something else?
Robert Latest <boblatest@yahoo.com>: Apr 27 07:22AM

["Followup-To:" header set to comp.lang.c.]
Juha Nieminen wrote:
>> (originally) have dynamically allocated members?
 
> But you yourself said that you don't automatically add manual constructors
> and destructors to every struct you write, just in case.
 
Of course not. When you design a struct type you should already know what it is
supposed to represent and if the represented things lend themselves to dynamic
allocation. If so, better write construction / deconstriction functions. It
hardly adds significant amounts of code
 
> "well-designed" C code one where every struct has a constructor and
> destructor function (that you always call for every single instance)? Or is
> it something else?
 
A certain amount of foresight is part of good planning. If you jump straight
into coding without paying attention to possible issues of extending and re-use
you may face some refactoring effort later. This of course goes for anything,
not just changing from static to dynamic allocation in C.
 
I suspect you once had to refactor some code with a few structs because you
forgot this planning and have now worked yourself into a fret about some
contrived example with "lots and lots" of struct types that need to be adapted.
I don't believe it's a real world case, but if it is, it's poorly planned.
 
--
robert
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: