Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Feb 25 12:34AM
On 24 Feb 2020 22:53:17 GMT > > Ken Thompson. > There's something wrong with that story: according to Wikipedia, Go > was designed in 2007. Here's a varbatim extract from what Rob Pike said about it in a talk in 2012 (text licensed by him under the Creative Commons Attribution 3.0 License). Please don't argue with me about whether he is right or not: take it up with him if you feel strongly about it. (I happen to think C++11 was pretty good.) Back around September 2007, I was doing some minor but central work on an enormous Google C++ program, one you've all interacted with, and my compilations were taking about 45 minutes on our huge distributed compile cluster. An announcement came around that there was going to be a talk presented by a couple of Google employees serving on the C++ standards committee. They were going to tell us what was coming in C++0x, as it was called at the time. (It's now known as C++11). In the span of an hour at that talk we heard about something like 35 new features that were being planned. In fact there were many more, but only 35 were described in the talk. Some of the features were minor, of course, but the ones in the talk were at least significant enough to call out. Some were very subtle and hard to understand, like rvalue references, while others are especially C++-like, such as variadic templates, and some others are just crazy, like user-defined literals. At this point I asked myself a question: Did the C++ committee really believe that was wrong with C++ was that it didn't have enough features? *** But the C++0x talk got me thinking again. One thing that really bothered me—and I think Ken and Robert as well—was the new C++ memory model with atomic types. It just felt wrong to put such a microscopically-defined set of details into an already over-burdened type system. It also seemed short-sighted, since it's likely that hardware will change significantly in the next decade and it would be unwise to couple the language too tightly to today's hardware. We returned to our offices after the talk. I started another compilation, turned my chair around to face Robert, and started asking pointed questions. Before the compilation was done, we'd roped Ken in and had decided to do something. We did not want to be writing in C++ forever, and we—me especially—wanted to have concurrency at my fingertips when writing Google code. We also wanted to address the problem of "programming in the large" head on, about which more later. |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 25 02:00AM -0500
Chris Vine wrote: > // and does not point to a Y object > const int g = q->z; // OK > const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK IMHO it is UB type punning either way (i.e. with or without std::launder) (I know that the code is from cppreference). Please see the whole argument in "Union type punning in C++ redux thread". Regardless of whether you agree to my position in there, I think in case of std::launder, the Standard is quite clear (for a change) so citing the complete subsection (by n4849): " 17.6.4 Pointer optimization barrier [ptr.launder] template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept; 1 Mandates: !is_function_v<T> && !is_void_v<T> is true. 2 Preconditions: p represents the address A of a byte in memory. An object X that is within its lifetime (6.7.3) and whose type is similar (7.3.5) to T is located at the address A. All bytes of storage that would be reachable through the result are reachable through p (see below). 3 Returns: A value of type T* that points to X. 4 Remarks: An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression. A byte of storage is reachable through a pointer value that points to an object Y if it is within the storage occupied by Y, an object that is pointer-interconvertible with Y, or the immediately-enclosing array object if Y is an array element. 5 [Note: If a new object is created in storage occupied by an existing object of the same type, a pointer to the original object can be used to refer to the new object unless its complete object is a const object or it is a base class subobject; in the latter cases, this function can be used to obtain a usable pointer to the new object. See 6.7.3. — end note] 6 [Example: struct X { int n; }; const X *p = new const X{3}; const int a = p->n; new (const_cast<X*>(p)) const X{5}; // p does not point to new object (6.7.3) // because its type is const const int b = p->n; // undefined behavior const int c = std::launder(p)->n; // OK — end example] " Please note: a) the name of the section, std::launder is a [compiler -- IMHO] *optimization barrier*. b) that to call std::launder the type of the object currently in memory shall be "similar" to the type argument of std::launder template and hence to the type to which p points. Note that how exactly the p is received does not matter (so the type of the object from which it was reinterpret_cast does not matter) -- it is exactly the point of std::launder to "build" (see below) from the pointer to the old object a pointer to the new object (of _similar type_ and through which all bytes of the storage occupied by the new object shall be _reachable_) occupying the same memory that the old object occupied before. c) the [Note:]. In Library descriptions, Notes are informative rather than normative so my guess it is here exactly and only to answer "why" question. The Note claims that, in general the pointer to the old object can be simply used to access the new object -- "unless its complete object is a const object or it is a base class subobject" -- as I briefly cited above -- which case is exactly the reason for launder to be called. [Note] hints at kinds of optimizations the "Pointer optimization barrier" intends to prohibit: in particular I would speculate that it needs to prohibit at least optimizing out reads via the old pointer-to-const or pointer-to-base subobject that compiler otherwise could replace with e.g. the values earlier loaded via the old pointer to registers (the former -- by reasoning that the const object shall not have changed; and the latter -- because the destruction and recreation of a derived object could be not traceable or not required by the Standard to be traced by the compiler if it can only see the code operating with a pointer to a base subobject of that derived object). d) finally, that the [Example] in the Standard is in direct and precise correspondence with the [Note] -- as opposed to the example at cppreference. Specifically, the [Example] does not use reinterpret_cast or static_cast distractions but instead demonstrates how the pointer to the destroyed const object can be used to build a pointer through which an object created later in the same storage ("new object") could be safely accessed. (At runtime, such "build" itself is probably no-op and the new pointer can very well have same value and even occupy same memory location or a register as the old pointer -- but at compile time the compiler would know to build the code that actually accesses the storage for new accesses via so built new pointer -- instead of using the values read earlier via the old pointer and cached somewhere. In other words, std::launder seems to be an explicit anti-aliasing of sort for similar types). |
Jorgen Grahn <grahn+nntp@snipabacken.se>: Feb 25 07:30AM
On Tue, 2020-02-25, Chris Vine wrote: > Here's a varbatim extract from what Rob Pike said about it in a talk in > 2012 (text licensed by him under the Creative Commons Attribution 3.0 > License). [snip quote] Thanks! It made more sense in his words. > Please don't argue with me about whether he is right or not: take it > up with him if you feel strongly about it. (I happen to think C++11 > was pretty good.) So do I -- to me, it's mostly a polished C++98, supporting the same things that C++98 did, only better. Although unlike Pike I don't care about concurrency ... I complained mostly because I didn't think work on (what became) C++11 had started in 2007, but thinking again of the C++0x name, of course it had. /Jorgen -- // Jorgen Grahn <grahn@ Oo o. . . \X/ snipabacken.se> O o . |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Feb 25 11:01AM
On 25 Feb 2020 07:30:10 GMT > On Tue, 2020-02-25, Chris Vine wrote: [snip] > So do I -- to me, it's mostly a polished C++98, supporting the same > things that C++98 did, only better. Although unlike Pike I don't care > about concurrency ... While I think that C++11 was pretty good - rvalue references and variadic templates were like a breath of fresh air - and I can see the point of concepts and modules in C++20, it seems to me that C++ is becoming so complex that it is beyond the ability of the normal programmer to come to grips with all of it. This results in bugs. C++ has grown as a loosely pinned set of features, not all of which are consistent and which are a mess of corner cases where one edge meets another. Some features look as if they were championed by dilletantes, not by practising programmers. It lacks regularity and consistency. C++ is not alone in complexity: Haskell for example is also complex and difficult to grasp (although it is largely regular), but Haskell is not a mainstream programming language like C++ is supposed to be. This doesn't worry me especially. I use other languages also (not yet rust), including functional languages, and enjoy them. However I do get annoyed by gratuitous code-breaking things in C++, like needing to apply std::launder to buffers updated with placement new. Surely we must all have written circular buffers which construct elements in place with placement new? What's the point with std::launder for heaven's sake - the compiler can see you have used placement new, that necessarily implies that the memory location concerned has been mutated, so when code accesses the buffer with the correct cast to meet aliasing rules, make sure the dereferenced values supplied are the correct ones. You shouldn't need to burden the programmer with having to remember to use std::launder to get the correct code generated. I am not convinced even the experts, such as those at cppreference.com or those on the standard committee, understand the rules on std::launder and placement new. I notice that C++20 has now rowed back from C++17 on some of this: in reponse to protestations, you no longer need to use std::launder to access an object constructed by placement new by reason of it having a const or reference member. However, nothing has changed on using std::launder with aligned char[]/std::byte[] buffers. I am still not 100% sure std::launder is required for this and that cppreference.com is right on the point, but nor does anyone else seem to be. |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Feb 25 12:13PM
On Tue, 25 Feb 2020 02:00:42 -0500 > know that the code is from cppreference). Please see the whole argument in > "Union type punning in C++ redux thread". Regardless of whether you agree to my > position in there, I completely disagree with you that leaving aside std::launder it is UB, so that does somewhat colour my view on the remainder of your views. It seems to me that you are just plucking this out of the air. Leaving aside std::launder, this is very well established code. If it didn't work there would be no point in having placement new (you couldn't use it in uninitialized memory returned by std::malloc() or operator new() either). Something similar appears in Stroustrup's TC++PL. > I think in case of std::launder, the Standard is quite clear > (for a change) so citing the complete subsection (by n4849): > 17.6.4 Pointer optimization barrier [ptr.launder] [snip] None of this deals with the issue now in question. §21.6.4 of C++17 is concerned with the preconditions to using std::launder (which would be met here) and the effects thereof, but not with the circumstances in which §6.8/8 of C++17 requires the use of std::launder. In the case cited above, on the face of it there are a number of questions going to the extent to which §6.8/8 of C++17 is relevant to begin with: i. By virtue of the implicit decay of arrays to pointers, is the array name, when combined with the pointer arithmetic used in accessing array members, a "pointer that pointed to the original object". ii. If so, does it cease to be so if (as above) the type of the buffer element type (std::byte) is different from the type constructed in it by placement new (type Y)? iii. Does §6.8/8 have any relevance where, as in the case cited above, placement new is used to construct an object in unninitialized memory for the first time? (On the face of it §6.8/8 is concerned with constructing a replacement object in initialized memory after the lifetime of the previous object has ended). |
Juha Nieminen <nospam@thanks.invalid>: Feb 25 01:51PM
> it seems to me that C++ is > becoming so complex that it is beyond the ability of the normal > programmer to come to grips with all of it. This is something that has been being said, non-stop, for the past 20 years. |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Feb 25 01:55PM
On Tue, 25 Feb 2020 12:13:36 +0000 Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote: [snip] > work there would be no point in having placement new (you couldn't use > it in uninitialized memory returned by std::malloc() or operator new() > either). Something similar appears in Stroustrup's TC++PL. I see it is also specifically permitted by §4.5/3 of C++17: If a complete object is created (8.3.4) in storage associated with another object e of type "array of N unsigned char" or of type "array of N std::byte" (21.2.1), that array provides storage for the created object if: * the lifetime of e has begun and not ended, and * the storage for the new object fits entirely within e, and * there is no smaller array object that satisfies these constraints. The way you create an object in storage associated with another object of array type is with placement new. |
Melzzzzz <Melzzzzz@zzzzz.com>: Feb 25 01:57PM
>> becoming so complex that it is beyond the ability of the normal >> programmer to come to grips with all of it. > This is something that has been being said, non-stop, for the past 20 years. Yeah, and response is very few use all of it :) -- press any key to continue or any other to quit... U ničemu ja ne uživam kao u svom statusu INVALIDA -- Zli Zec Svi smo svedoci - oko 3 godine intenzivne propagande je dovoljno da jedan narod poludi -- Zli Zec Na divljem zapadu i nije bilo tako puno nasilja, upravo zato jer su svi bili naoruzani. -- Mladen Gogala |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Feb 25 02:40PM
On Tue, 25 Feb 2020 13:51:55 -0000 (UTC) > > becoming so complex that it is beyond the ability of the normal > > programmer to come to grips with all of it. > This is something that has been being said, non-stop, for the past 20 years. And the relative use of C++ in projects has dropped in the past 20 years also. Whether that's because people are put off by C++'s complexity and proneness to having undefined behavior spring out to get you, or because they think other languages are better in other ways, is difficult to say. Probably factors such as garbage collection, developer support and type safety are also relevant. |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 25 12:26AM -0500
Bonita Montero wrote: >> Care to pick one that is most readable in your opinion for me to demonstrate its >> readability pitfalls? > Pople able to manage to handle C++ can read either case good. Popla manage not handle good. |
Bonita Montero <Bonita.Montero@gmail.com>: Feb 19 07:28AM +0100
> I have never said it had bad readability. I said your "nice thing" serving > no purpose was an obfuscation pretending to be simple while not being so. ... Whoever finds the contradiction may keep it. > return TSX_ABORTED_CAN_RETRY; > return TSX_ABORTED_DONT_RETRY; > } That's a matter of taste. |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 17 04:04PM -0500
Bonita Montero wrote: >>> some nice things like this "++(xyz == CMP ? a : b)" with it. >> Now THAT is a) obfuscation and b) irrelevant to your TM idea. > It's unusual, but readable, so no obfuscation. It's not really unusual. You "nice thing" pretends to be simple (which it is not) only because it's example code serving no useful purpose. Your original code served useful purpose and you immediately found you had to correct it. The correction made your conditional expression slightly bulkier and the use of ternary operator (that was originally reasonable) lost 95% of its initial appeal. It's no longer concise and not too readable even though it is free of side effects as opposed to your "nice thing" above. Readable code separates concerns and your "nice thing" does the opposite. |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 19 12:28AM -0500
Bonita Montero wrote: >> your "nice thing" above. Readable code separates concerns and your "nice >> thing" does the opposite. > The code I've shown doesn't have a bad redability. I have never said it had bad readability. I said your "nice thing" serving no purpose was an obfuscation pretending to be simple while not being so. It would be a bad idea (now I am saying "bad") to use it as an example to do anything useful. Your original "transaction" code was rather readable although had an unnecessary "else" clause made it more complex than necessary. While fixing the bug your worsened the readability from ok to rather poor (again I am not calling it "bad"). A better readable code for "transaction", after the bug fix and preserving your preferences for indentation and braces but not new lines and using magic numbers vs symbolic constants could be, for example: enum TsxStatusCategory: int { TSX_ABORTED_DONT_RETRY = -1, TSX_ABORTED_CAN_RETRY = 0, TSX_COMMITTED = 1 }; template<typename L> inline TsxStatusCategory doTsxTransaction( L &lambda ) // function name should be a verb [clause] { unsigned code = _xbegin(); // unsinged is not unsigned BTW if( code == _XBEGIN_STARTED ) { lambda(); _xend(); return TSX_COMMITTED; } // `else' served no purpose here other than obfuscation if (code & _XABORT_EXPLICIT) return TSX_ABORTED_DONT_RETRY; if (code & _XABORT_RETRY) return TSX_ABORTED_CAN_RETRY; return TSX_ABORTED_DONT_RETRY; } > Yours has a bad readabiliy. Yes, it does and as said earlier it is its point. |
Bonita Montero <Bonita.Montero@gmail.com>: Feb 18 08:09AM +0100
> not too readable even though it is free of side effects as opposed to > your "nice thing" above. Readable code separates concerns and your "nice > thing" does the opposite. The code I've shown doesn't have a bad redability. Yours has a bad readabiliy. |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 25 12:03AM -0500
Öö Tiib wrote: > to struct itself I already copy pasted. And aligned_storage > is satisfying the alignment requirements of T so why you deny > that Daniel's example is well-formed? Because the above text requires that "there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible (6.9.2) with a" for result to be a pointer to b. How can we deduce that the type std::aligned_storage<T, some-align-greater-than-or-equal-to-alignof<T> >::type is pointer-interconvertible with T (even ignoring cv-qualification)? |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 25 12:11AM -0500
Pavel wrote: > How can we deduce that the type std::aligned_storage<T, > some-align-greater-than-or-equal-to-alignof<T> >::type is > pointer-interconvertible with T (even ignoring cv-qualification)? More precisely I should have asked: \ How can we deduce that *an object of type* std::aligned_storage<T, some-align-greater-than-or-equal-to-alignof<T> >::type is pointer-interconvertible with *an object of type* T (even ignoring cv-qualification)? |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 25 12:19AM -0500
Cholo Lennon wrote: > Just for the record: there is a nice talk from CppCon 2019 about "Type punning > in modern C++" > https://youtu.be/_qzMpk-22cc Thanks, it is a nice and somewhat funny talk. Among the others, there is a chance that the OP's code will become legal in C++ 23 :-). The guy is in "reinterpret_cast" camp but in the Q&A session he at least acknowledges the existence of the "union" camp that believes that no use of reinterpret_cast is legal for type punning. It seems someone from that other camp wrote an article to disprove reinterpret_cast type punning .. funny I never heard of it; will try to find and read it when idle i.e. probably also around 2023 when it really does not matter :-). > Cholo Lennon > Bs.As. > ARG -Pavel |
"Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Feb 24 10:37PM -0800
On 2/24/2020 9:19 PM, Pavel wrote: > camp wrote an article to disprove reinterpret_cast type punning .. funny I never > heard of it; will try to find and read it when idle i.e. probably also around > 2023 when it really does not matter :-). Fwiw, what I get from the talk is that its better have a ctor and dtor, or else its undefined regardless? Placement new and explicit dtor call for properly aligned bytes. |
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo>: Feb 19 01:21AM -0500
Daniel wrote: > return a.tag; > } > }; I think no: it is not allowed to inspect a.tag regardless of the constructor used to construct V because `tag' is not a part of the common initial sequence of either V::a and V::b or of V::a and V::c (I think the common initial sequence is empty in both cases). > { > } > }; I think no, for same reason. I think changing implementation of tag() to either of { return b.tag; } or { return c.tag; } would make the code valid (then of course having V::a member would be unnecessary). > Thanks, > Daniel FWIW, -Pavel |