- Text editor project in C - 14 Updates
- C++ may sometimes be *too* simple (to use) - 2 Updates
- VS2019 and template specialisation - 1 Update
- Overloading the [] operator for read/write... - 2 Updates
- std::bind with member functions and C++20 - 4 Updates
- No, C is not a simple language - 2 Updates
| 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:
Post a Comment