- [newbie] visibility again - 1 Update
- Just some code - 6 Updates
- Operators for min/max, or min/max-assignment operators? - 7 Updates
- How link LLVM? - 1 Update
- Reset struct or class sections - 9 Updates
- auto return type - 1 Update
Soviet_Mario <SovietMario@CCCP.MIR>: Jul 01 01:35PM +0200 I'm getting different errors when compiling and changing the order of the files. Now I still have clear enough notion of SCOPE of object, but I must admit I have none about the sequence of scopes (the "order" the files are compiled or, maybe, linked). Is this relevant ? For a proof I merged the content of four files that generated errors (type not defined and so) and as a monolitic file it compiles and links without error, so the problem is in the order of declarations, but I don't know well this topic. If there is sb using QT creator, how is possible to control this in the IDE ? Or even better (but I'm not optimistic) : is it possible to embed in CODE itself directives, pragmas or else, to control the flow of compilation or linking ? I tried to resort to include, but it didn't work ... TY -- 1) Resistere, resistere, resistere. 2) Se tutti pagano le tasse, le tasse le pagano tutti Soviet_Mario - (aka Gatto_Vizzato) |
Tim Rentsch <txr@alumni.caltech.edu>: Jun 30 04:51PM -0700 > I can't recall having seen this approach, so posting it here: > [..code was here..] Interesting idea. Perhaps a little weird, but definitely interesting! Here is a somewhat different means of arranging the cleanup. Note the single constructor call for 'Later it' below: #include <iostream> #include <string> #include <functional> #include <sstream> using namespace std; using Fixer = function< void() >; class Later { Fixer f; public: Later( Fixer f0 ) : f( f0 ) {} ~Later(){ f(); } }; int main( int argc, char *argv[] ){ Fixer supersede_with( const char * ), use_stdin(); bool faux = argc > 1 && string( argv[1] ) == "--faux-text"; cout << " With" << (faux ? "" : "out") << " other input:\n"; Later it( faux ? supersede_with( "Your ad here!" ) : use_stdin() ); cout << " (here we are running the app)\n"; } Fixer supersede_with( const char *other_input ){ cout << " 'supersede_with' starting - " << other_input << '\n'; istringstream in{ basic_string<char>( other_input ) }; streambuf *s{ cin.rdbuf() }; cin.rdbuf( in.rdbuf() ); return [=](){ cin.rdbuf( s ); cout << " 'supersede_with' ending - " << other_input << "\n"; }; } Fixer use_stdin(){ cout << " (any tests before using regular input...)\n"; return [](){ cout << " (we have been using regular input)\n"; }; } Disclaimer: I am certainly not an expert in C++ lambdas. AFAICT the above code is okay, but I confess my efforts to decipher what the C++ standard says could not be called entirely successful. Running the program produced the expected output in both cases. (Also thanks due to Manfred, from whose program I cribbed a bit.) |
Tim Rentsch <txr@alumni.caltech.edu>: Jun 30 11:07PM -0700 > On 06/27/2018 07:10 AM, Alf P. Steinbach wrote: >> On 27.06.2018 09:36, David Brown wrote: [...] > initializers, or conditions in #if directives or if(), while(), do > while(), and for() statements, or the first and third parts of a > for(). What nonsense. There are plenty of places ?: can be used as part of ordinary expressions that make a program shorter, simpler, easier to read, and easier to understand. > Even in those locations, it should only be used if it's actually > clearer than the alternative (which would, generically, involve > defining a function that contains those multiple statements). The word "clearer" in this context is sufficiently vague and subjective that this statement has almost no semantic content. |
Christian Gollwitzer <auriocus@gmx.de>: Jul 01 08:48AM +0200 Am 29.06.18 um 06:08 schrieb Alf P. Steinbach: >> app::run(); > Here cleanup is performed before `app::run()`, which means it will never > get executed with faux text. As a non-expert C++ programmer, I could understand the intent of the code and the intricacies of the , operator only after it was explained here by several people. The ?: is not the problem, but in this code I was heavily wondering how the hell can the app::run() access anything from these with_stream... objects. The problem is the data flow via side effects. It would have been different if it looked like app::run(args[1] == "--faux-text" ? with_stream() : with_faux()); Or perhaps: app App; app.setStream(args[1] == "--faux-text" ? with_stream() : with_faux()) app.run(); And instead of with_stream() etc. modifying global objects, it should return a stream object to be read from. In that case it is very clear how the information passes on to app::run. Modifying a global state from within a temporary which then sets it back in the destructor is an example of very tricky interaction and IMHO bad code. Christian |
Tim Rentsch <txr@alumni.caltech.edu>: Jul 01 12:29AM -0700 > Has the conditional turned into a somewhat incomprehensible > monstrosity already in 2, or is it the use of a data member, as in 3, > that makes their eyes glaze over? Let me offer some purely personal reactions (not just on the above question but on the thread topic generally). First I think the idea that ?: is somehow "too difficult" (for some unspecified set of programmers) is overblown. If someone can't understand the ?: operator inside of 15 minutes they shouldn't be programming at all, full stop. Second, the trick with doing a member access probably deserves a comment, but nothing more than that. Third, ditto the first remark, for the comma operator. I don't see anything wrong with the usage of either ?: or the comma operator in the original code. Fourth, I didn't know the rule about lifetime of temporary objects but it doesn't surprise me either. Let me pretend that I was thoroughly familiar with that rule when reading the code. Now, despite all the foregoing, I still have a funny reaction to the original expression overall. There are several reasons that might contribute to this feeling. One, it's unusual to see an object constructed conditionally - normally it's either there or it isn't. Two, even knowing the rules for the comma operator and temporary object lifetime, it's surprising to discover how they interact here; I might say that part of my brain "knows" that when control gets to a comma operator, the left-hand operand expression is "done". Three, I can't escape the feeling that somehow the construction is "sneaky", or that it expresses what is meant in a less direct way than it could. I'm reminded of how I felt the first time I saw Duff's device - I understood all the pieces just fine, but the effect of the combination was still suprising. In the case of Duff's device there was a particular motivation that offset the surprise factor, and I think that was a good decision (ie, in the original circumstances). Here I don't know what the offsetting motivation would be. The various forces you have mentioned - RAII, factoring, etc - all are worth observing, but none of them is absolute. I think this is one of those rare cases where one of the "inviolable" rules needs to be broken, because not doing that would lead to a worse result. So fwiw, there is my own sense of the situation. Not saying anyone else should feel the same way, just that this is how it strikes me. |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Jul 01 10:27AM +0200 On 01.07.2018 08:48, Christian Gollwitzer wrote: > was heavily wondering how the hell can the app::run() access anything > from these with_stream... objects. The problem is the data flow via side > effects. This, how the code appears to you, is interesting, because the RAII side-effect variable general technique that is problematic to you, and presumably also to the other critics, is idiomatic except that usually the variable is technically needlessly /named/. This example is from ¹cppreference.com, std::lock_guard<std::mutex> guard(g_pages_mutex); g_pages[url] = result; For this use the name "guard" isn't ever used for anything. It just possibly can produce a warning from the compiler, like hey, you forgot to use that thing... Which possible warning can be suppressed by writing std::lock_guard<std::mutex> guard(g_pages_mutex); (void) guard; g_pages[url] = result; But better, in my opinion, is to just never introduce that extraneous misleading not-used-for-anything name in the first place: std::lock_guard<std::mutex>{ g_pages_mutex } , g_pages[url] = result; So that's the technical background: it's a cleaned up version, namely, no unused name, of a very common C++ idiom. * * * A technical alternative to the "data flow via side effects" where cin is modified, could be to pass an istream reference down the call chain instead of directly using cin in app::run and lower. But that would be a parameter explicitly propagated down the call chain just to support the faux text used in the software development. That feels very wrong to me, very Microsoft-ish :(, both because it would misleadingly communicate to readers of the code that this generalization was there to support the ordinary purpose of the code, and because it would add a technically unnecessary cost to every function in the call chain just to support readability at a top level. > It would have been different if it looked like > app::run(args[1] == "--faux-text" ? with_stream() : with_faux()); As a general technique that seems to get impractical, adding unnecessary complication. E.g. here's the earlier cppreference example reworked to use that technique: void set_pages_entry( const std::string& key, const std::string& value, const std::lock_guard<std::mutex>& // lets caller pass object ) { g_pages[key] = value; } //... set_pages_entry( url, result, std::lock_guard<std::mutex>{g_pages_mutex} ); Of course the mutex locking can and probably should be moved into that function, given that one wants to keep the function, like this: void set_pages_entry( const std::string& key, const std::string& value ) { std::lock_guard<std::mutex>{ g_pages_mutex } , g_pages[key] = value; } //... set_pages_entry( url, result ); But now there's something eerily familiar about the function body code... By adopting the suggested pass-it-in-a-function-call technique, and cleaning up the resulting code, we got back where we started. Except that now there's an added function. > app App; > app.setStream(args[1] == "--faux-text" ? with_stream() : with_faux()) > app.run(); Not sure what you mean here. Instead of having the global state that affects `cin` in `cin`, have the global state that affects `cin` in an object called `app`? > Modifying a global state from within a temporary which then sets it back > in the destructor is an example of very tricky interaction and IMHO bad > code. Well, /that part/ is idiomatic, except for using a technically unnecessary name that might produce a warning about being unused. I never understood why such name is always used. But now I understand it some, that the name, or perhaps the separation into distinct statements that it allows, aids in code comprehension? Cheers!, - Alf Notes: ¹ <url: https://en.cppreference.com/w/cpp/thread/mutex#Example> |
Manfred <noname@invalid.add>: Jul 01 01:35PM +0200 On 7/1/2018 10:27 AM, Alf P. Steinbach wrote: > , g_pages[url] = result; > So that's the technical background: it's a cleaned up version, namely, > no unused name, of a very common C++ idiom. The intent is clear, and the idiomatic background is clear too. Still I have a few considerations: Your approach is heavily based on the lifetime of a temporary, which, being unnamed, is hidden from the explicit flow of the code. In common C++ programming, the lifetime of temporaries is typically handled as something "the compiler will take care of", and the programmer typically wants to care little more than the upper limit of its lifetime span: the terminating ; of the statement. In this case (the original one, not the lock guard) you are effectively defining an execution context for the app. This is worth a name, IMO. Moreover, the whole context object (With_faux_text or With_stream_detection) is hanging on the obscure '_' member - and one has to think about the guarantee that this actually ensures that the whole object is kept alive. In short, your proposal works, but instead of /using/ the syntax to express an intent (which is what programmers do), it constrains the coding of the intent within a chosen syntax. * * * That said, your recalling of the lock guard raises a different consideration - when writing: std::lock_guard<std::mutex> guard(g_pages_mutex); what I still find annoying is that you may forget to name the thing (because you don't use the name afterwards), and the resulting statement: std::lock_guard<std::mutex>(g_pages_mutex); will compile, but be ineffective - most annoyingly, /silently/ ineffective (unless you have a compiler that can catch this stuff, and you instruct it to do so). [Microsoft has handled this its way, i.e with an ad-hoc solution in C# they have a dedicated lock statement which reads: lock(mutex_object) { ... } ] So, I think you actually have a point with respect to the unnecessary name, but not just because it is /cleaner/ code, but because since it is unused after declaration, it can be forgotten and lead to a non-trivial error (non trivial because compilers may miss it). |
Wouter Verhelst <w@uter.be>: Jul 01 09:23AM +0200 On 30-06-18 13:17, Bart wrote: > If min/max were operators, so that they could be written as 'a max b', > perhaps as well as 'max(a,b)', then the following becomes possible: > a max= b; Which can be written as a = max(a, b); just as well > as well as: > A[++i] max= lowerlim; A[++i] = max(A[i], b); ... and then max just needs to either be a safe macro or an inline function. With gcc, you can make it a safe macro like so: #define max(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a > _b ? _a : _b; }) typeof is a gcc extension that evaluates to the type of the expression, without evaluating the expression (so it has no side effects), except as necessary to do what it needs to do: #include <stdio.h> int main(void) { int i = 0; char c = 2; typeof(i++) j = i; printf("%d %d\n", i, j); // output: 0 0 typeof(++i == 1 ? i : c) c2 = c; printf("%d %d\n", i, (int)c2); // output: 0 2 } the ({ ...code... }) syntax is also a gcc extension called a "statement expression", which evaluates to the value of the final expression in the inner code block. https://gcc.gnu.org/onlinedocs/gcc-8.1.0/gcc/Statement-Exprs.html > (And actually, the x64 processor has native MIN and MAX instructions for > floating point; presumably they were considered useful enough to > implement in hardware.) I'm sure a decent optimizing compiler will optimize something like a max function or safe macro implemented with a trinary operator or a simple if() to that instruction. |
Barry Schwarz <schwarzb@dqel.com>: Jul 01 01:54AM -0700 >> as well as: >> A[++i] max= lowerlim; >A[++i] = max(A[i], b); Unfortunately, this invokes undefined behavior. -- Remove del for email |
Bart <bc@freeuk.com>: Jul 01 10:08AM +0100 On 01/07/2018 08:23, Wouter Verhelst wrote: >> perhaps as well as 'max(a,b)', then the following becomes possible: >> a max= b; > Which can be written as a = max(a, b); just as well Sure. As can 'a += b'. But there must be a reason why such 'assignment operators' (not 'augmented assignment' as I thought they were called) were present even in early C. > I'm sure a decent optimizing compiler will optimize something like a max > function or safe macro implemented with a trinary operator or a simple > if() to that instruction. I don't use an optimising compiler. Evaluating 'max(a,b)' when a,b are ints, and 'max' is a function (not a macro, not even an inline function) involves executing some 14 instructions, including call, return and branches [on x64]. But when 'max' is an intrinsic operator, then it can be trivially done in 4 instructions when a and b are int (3 is possible), and 2 instructions when they are floats, none of which are branches. You also get the advantage of overloading via the usual operator mechanisms, without needing to rely on anything else (safe macros, overloaded functions etc). -- bart |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Jul 01 12:15PM +0200 On 01.07.2018 11:08, Bart wrote: > But there must be a reason why such 'assignment operators' (not > 'augmented assignment' as I thought they were called) were present even > in early C. They corresponded directly, and still correspond, to very common processor instructions, and at that time it was more the programmer's job, and not the compiler's, to optimize the resulting machine code. Typically an integer add instruction, say, adds to a register, like on the i8086 (the original IBM PC processor, except for data bus width): add ax, bx ; add the contents of register ax, to bx So in C and C++: ax += bx; Of course, historically that was ten years or so after development of C started, PC 1981 versus plain C 1971. I think the original C development was on a PDP-10. I had some limited exposure to the PDP-11, but all I remember about the assembly language was that it was peppered with @ signs (probably indicating macros), and that the registers were numbered and memory-mapped. But I'm pretty sure that if the PDP-11 didn't have add to register, I'd remember that. And so, presumably also the PDP-10. Disclaimer: maybe C development started a year or two later. And maybe it was a PDP-9, if such a beast existed. Google could probably cough up the more exact history, but it doesn't matter here. [snip] Cheers!, - Alf |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Jul 01 12:17PM +0200 On 01.07.2018 12:15, Alf P. Steinbach wrote: > Typically an integer add instruction, say, adds to a register, like on > the i8086 (the original IBM PC processor, except for data bus width): > add ax, bx ; add the contents of register ax, to bx "of register bx, to ax" > So in C and C++: > ax += bx; > [snip] Sorry, - Alf |
Ben Bacarisse <ben.usenet@bsb.me.uk>: Jul 01 12:20PM +0100 > On 01.07.2018 11:08, Bart wrote: <snip> > They corresponded directly, and still correspond, to very common > processor instructions, and at that time it was more the programmer's > job, and not the compiler's, to optimize the resulting machine code. I think that is an unlikely explanation. First, they came into C from Algol68 via B, and Algol68 never had any intention of providing operators for the purpose of helping the programmer optimise code! Secondly, there never was a B compiler in the traditional sense of one that generated machine instructions. However, there's a grain of truth here. B was implemented as "threaded code" where each operation is translated into a jump to the code that performs the appropriate action. There was no possibility of doing any optimisation in 8K (on a PDP-7) but a =+ b was bound to be more efficient that a = a + b simply because it could be translated to single jump. > I think the original C development was on a PDP-10. Even "New B" development was done on the PDP-11 and anything that can reasonably be called C certainly was. > Disclaimer: maybe C development started a year or two later. And maybe > it was a PDP-9, if such a beast existed. B was born on the PDP-7 and C on the PDP-11. > Google could probably cough > up the more exact history, but it doesn't matter here. True, but it's nice to keep the history straight. -- Ben. |
Bart <bc@freeuk.com>: Jul 01 12:20PM +0100 On 01/07/2018 11:15, Alf P. Steinbach wrote: > They corresponded directly, and still correspond, to very common > processor instructions, and at that time it was more the programmer's > job, and not the compiler's, to optimize the resulting machine code. Sometimes they will correspond to hardware instructions, other times they don't (eg. floating point on modern x86). Such assignments were of part of Algol68 (created around 1968 I believe), and there were doubtless earlier precedents. They are useful to better express intent, and make it easier for a compiler to generate code (otherwise they have to decide where a[f(x)].b = a[f(x)].b + 1 is the same thing as a[f(x)].b += 1; it's a lot easier if the programmer just writes the latter). They can also indicate something subtly different from A = A+1, as in my more complex example, where f(x) with possible side-effects might be called once or twice. Also in this example (not from C): s = s + "A" This takes a string s, creates a new string with "A" appended, then assigns back into s, destroying the original. Lots of string processing going on. But write it like this: s += "A" and now it can be taken to mean in-place append. Given adequate capacity, which is usually the case, this now writes adds one byte onto the end of s, and updates its length. > signs (probably indicating macros), and that the registers were numbered > and memory-mapped. But I'm pretty sure that if the PDP-11 didn't have > add to register, I'd remember that. And so, presumably also the PDP-10. I don't think C was developed for PDP10, not at first anyway. But as I said, augmented assignment was not just limited to C, it has other benefits that just mapping neatly to hardware instructions. -- bart |
Borneq <borneq@antyspam.hidden.pl>: Jul 01 10:23AM +0200 I have small project which uses LLVM. LLVM under Linux has 134 *.a files counting 5 gigabytes. I can't add everything to CodeBlocks project. |
"Rick C. Hodgin" <rick.c.hodgin@gmail.com>: Jun 30 07:26PM -0400 On 06/30/2018 07:02 PM, Chris M. Thomasson wrote: > struct parts p = { { 12 }, { 34 } }; > p.p1.b = 42; > ____________________________ I considered that solution. It has a few fundamental flaws. Namely: You have to reference struct components with additional naming steps, and even if you make them anonymous you still can't have members which overlap from one struct into the next. > ;^) :-( -- Rick C. Hodgin |
Bart <bc@freeuk.com>: Jul 01 12:55AM +0100 On 30/06/2018 23:45, Rick C. Hodgin wrote: > I devised it. And it's not ugly or confusing. With syntax highlighting > applied there are color bars which differentiate each grouping, and the > section headers are hidden. Effectively you're taking the elements of a struct, ignoring any existing nested structure, and treating it as a mixed-type array, with sections defining random slices of that array. Since you can't really have a mixed-type array, your are actually superimposing multiple different structs - non-nested ones - on top of arbitrary portions of a regular struct. It sounds interesting but I'm struggling to think of a use which is not better handled by redesigning the original struct to better suit the requirements. > is criticized by you in your replies? It's a bad idea as I propose it, > or it doesn't add enough value to be useful, or its just clutters up > something, etc.? Maybe it's just me but I have a really hard time understanding the point of your proposals. I think new features should be kept to a minimum and the ones that are added should be obviously useful without needing to scratch your head wondering under what circumstances they might ever be used. > create one. But I do have a need for this ability, and it's not that > difficult to add to the compiler, and I can see it have great utility > when you stop and give the potential uses some thought. If I really wanted something like that, I would probably implement it as something like a slice. That is, a span from field .a to field .b, where a, b and the fields in-between can all be different types. The declaration of the struct needs no extra annotations; and the same struct can be sliced in different ways by different programs, if it is shared. In fact, the slice can be applied to a struct declared in a third party header. (As for syntax, that would need some thought, but could be as simple as X.(a,b) where X is a struct expression, and a,b are field names.) -- bart |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Jun 30 05:00PM -0700 On 6/30/2018 4:26 PM, Rick C. Hodgin wrote: >> ____________________________ > I considered that solution. It has a few fundamental flaws. Namely: > You have to reference struct components with additional naming steps, Consistent with C. > and even if you make them anonymous you still can't have members which > overlap from one struct into the next. Are you really that worried about the naming steps: p.p1.b ? Like trying to spread some sort of syntactic sugar all over the place? What about the ants? ;^) |
"Rick C. Hodgin" <rick.c.hodgin@gmail.com>: Jul 01 12:06AM -0400 On 06/30/2018 07:55 PM, Bart wrote: > It sounds interesting but I'm struggling to think of a use which is not > better handled by redesigning the original struct to better suit the > requirements. It allows you to send only portions of your parent object to external things, making only those portions visible to it. > the ones that are added should be obviously useful without needing to > scratch your head wondering under what circumstances they might ever be > used. Pretty much every idea I've had has occurred to me while coding. I've had some need to do something, and the tool wouldn't allow it to be done, so I've gone to define it. > If I really wanted something like that, I would probably implement it as > something like a slice. That is, a span from field .a to field .b, where > a, b and the fields in-between can all be different types. The section definitions I stated above could work with slices. You could append single lines or multiple separate blocks. It basically creates a protocol to allow sub-portions of the encap- sulating object to be exposed externally. -- Rick C. Hodgin |
"Rick C. Hodgin" <rick.c.hodgin@gmail.com>: Jul 01 12:08AM -0400 On 06/30/2018 08:00 PM, Chris M. Thomasson wrote: >> overlap from one struct into the next. > Are you really that worried about the naming steps: > p.p1.b ? Worried? No. It's just ridiculous to force such a thing on people. -- Rick C. Hodgin |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Jun 30 09:58PM -0700 On 6/30/2018 9:08 PM, Rick C. Hodgin wrote: >> Are you really that worried about the naming steps: >> p.p1.b ? > Worried? No. It's just ridiculous to force such a thing on people. Why is that so "ridiculous"? It sure allows one to know exactly what they are referring to; It sure shaves off a lot of ambiguity. ;^) |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Jun 30 09:59PM -0700 On 6/30/2018 9:06 PM, Rick C. Hodgin wrote: > could append single lines or multiple separate blocks. > It basically creates a protocol to allow sub-portions of the encap- > sulating object to be exposed externally. Okay, but it seems like adding some extra, perhaps unnecessary sugar to the mix. Might have to see a dentist. |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Jul 01 12:48AM -0700 On 6/30/2018 9:59 PM, Chris M. Thomasson wrote: >> sulating object to be exposed externally. > Okay, but it seems like adding some extra, perhaps unnecessary sugar to > the mix. Might have to see a dentist. Perhaps it can be handy from time to time. Need to think on it. |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Jul 01 12:54AM -0700 On 7/1/2018 12:48 AM, Chris M. Thomasson wrote: >> Okay, but it seems like adding some extra, perhaps unnecessary sugar >> to the mix. Might have to see a dentist. > Perhaps it can be handy from time to time. Need to think on it. You would have to explicitly document this feature in the CAlive archives. And perhaps try to think of giving out some simple highly experimental online compiler examples on how to use it in a very experimental space. Think producing an interactive tutorial, to go along with your highly adaptive, user friendly and extraordinarily integrated debugger! I have to admit walking through code in a GUI debugger before.... MSVC was always a nice one, indeed. :^) |
"Chris M. Thomasson" <invalid_chris_thomasson@invalid.invalid>: Jul 01 12:45AM -0700 On 6/27/2018 2:37 PM, Chris M. Thomasson wrote: >> Nope, the tools should be updated to cope with the new language rules. >> However, auto return type should be avoided in API-s > Agreed. The return value of a function in an API exported from a shared lib, say DLL or .so, must be concrete? This is what I am talking about wrt API. Not a C++ header wrt API that can contain all sorts of fancy things... ;^) |
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