tag:blogger.com,1999:blog-7101933101966798446.post4894445800825638155..comments2024-03-28T10:33:06.910-07:00Comments on The View from Aristeia: Book Report: New Title, New TOC, New Sample ItemScott Meyershttp://www.blogger.com/profile/05280964633768289328noreply@blogger.comBlogger53125tag:blogger.com,1999:blog-7101933101966798446.post-6449159331481328902014-04-09T19:08:29.382-07:002014-04-09T19:08:29.382-07:00Great Scott I am a keen follower of your Effective...Great Scott I am a keen follower of your Effective series.<br />Let your book name be Effective ****<br />Anonymoushttps://www.blogger.com/profile/02680934919013583801noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-24822032850643470952014-04-09T13:56:57.162-07:002014-04-09T13:56:57.162-07:00Then again, I suppose you could also force copy/mo...Then again, I suppose you could also force copy/move construction if doSomeWork() used braces internally:<br /><br />doSomeWork(v, decltype(v)(10,20)); // 10 elements of value 20<br /><br />doSomeWork(v, 10, 20); // 2 elements of values 10, 20<br /><br />But I still like that make_shared and make_unique use parentheses internally because you have to use parentheses as part of the function call anyway.Alex Howletthttps://www.blogger.com/profile/01043149701435832314noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-40191351481753414322014-04-09T05:08:10.365-07:002014-04-09T05:08:10.365-07:00Posting again with angle brackets fixed:
Wow. Go...Posting again with angle brackets fixed:<br /><br />Wow. Good point. I could have sworn I'd done something like that before, but I guess not. I wonder why they chose to deduce braced initializers to std::initializer_list for auto, but not for templates.<br /><br />I suppose you'd have to do this:<br /><br />doSomeWork(v, std::initializer_list<int>{10, 20});<br /><br />Using parentheses internally still allows the caller to choose the list constructor.<br /><br />For example, I just tried this in both GCC 4.8.1 and Visual C++ 2013 and it worked as expected:<br /><br />auto vec1 = make_shared<vector<int>>(initializer_list<int>{10, 20}); // 2 elements of values 10, 20<br />auto vec2 = make_shared<vector<int>>(10,20); // 10 elements of value 20Alex Howletthttps://www.blogger.com/profile/01043149701435832314noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-85151281777258141472014-04-08T17:30:42.043-07:002014-04-08T17:30:42.043-07:00@Alex Howlett: Thanks for your extensive remarks. ...@Alex Howlett: Thanks for your extensive remarks. My only comment is that <br /><br />doSomeWork(v, {10, 20});<br /><br />won't compile, because you can't perfect-forward braced initializers. That's why you can't use std::make_unique or std::make_shared to create an object using list initialization.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-12966849534093642852014-04-08T00:02:00.447-07:002014-04-08T00:02:00.447-07:00Whew. This got long. Here's part 2:
On page...Whew. This got long. Here's part 2:<br /><br />On page 4 when discussing constructor calls, instead of saying:<br /><br />"In constructor calls, parentheses and braces have the same meaning as long as std::initializer_list parameters are not involved."<br /><br />you could say:<br /><br />"Empty list initialization calls the default constructor. Non-empty list initialization calls a matching list constructor. In the absence of a suitable list constructor, the elements of the list are considered as arguments for overload resolution on the other constructors."<br /><br />Everything about the "compiler's determination to match braced initializers" makes perfect sense with this interpretation. And I don't really feel like the empty list initialization warrants being called out as a twist on pages 6-7.<br /><br />On page 7, I'd be okay with it if you left out your second "twist" entirely along with its example. As far as arcane rules about braced initializers goes, I don't think this particular one will matter on a day-to-day basis.<br /><br />On page 8, I think you hit the nail on the head with the std::vector example. The standard containers are where {} vs () is most likely to trip people up.<br /><br />For the first takeaway, more succinct advice could be:<br /><br />"Only add list constructors to classes that represent containers and only for the purpose of initializing their elements."<br /><br />Also, I can't think of when it would be a good idea to add a list constructor without also adding a list assignment operator. Rule of 2?<br /><br />On page 9, a more succinct second takeaway could be:<br /><br />"When using containers, braced initialization provides an initial list of elements."<br /><br />On pages 10-11, I feel like the doSomeWork example should *always* use parentheses internally because you can still call the list constructor by calling doSomeWork like this:<br /><br />doSomeWork(v, {10, 20});<br /><br />The reverse is not true. You can't get the "normal" constructor if you use braces the template body. I can't think of an example for which it would be preferable to use braces.Alex Howletthttps://www.blogger.com/profile/01043149701435832314noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-44625303875451816592014-04-08T00:01:10.958-07:002014-04-08T00:01:10.958-07:00That's a good point about the performance of a...That's a good point about the performance of assignment vs. copy initialization. What I meant by "properly" is that the assignment operator is implemented in such a way that you end up with the same object regardless of whether you copy initialized it or or default constructed it and then assigned to it. Is there a big downside to a newbie being misled into thinking an assignment is taking place?<br /><br />And yes, I don't think it would hurt to eliminate lines 7-21.<br /><br />You could also simplify the item by leaving out copy initialization (with equals sign) entirely and only using direct initialization (without equals sign). Then, instead of comparing three different forms of initialization, you'd only be comparing two.<br /><br />You could then write a separate item that discusses the difference between direct initialization and copy initialization. This is where you could mention the std::atomic example from page 3.<br /><br />In your previous books, I'm used to each item representing a succinct nugget of advice or rule of thumb. I'm not getting that vibe from item 7. Is it necessary to enumerate all the differences between parentheses-based initialization and braced initialization? Or could it make more sense to give a single general piece of advice that helps people intuitively navigate the differences?<br /><br />For example, if we treat values enclosed in {} as "lists of elements," it becomes obvious that make_unique and make_shared *should* use parentheses internally. The behavior of auto is intuitive rather than surprising if you think of {} as a list that's convertable to the type of its single element. The fact that {} prevents narrowing conversions is then a consequence of "boxing" the value in a list.<br /><br />It also explains why this works:<br /><br />for (auto i : {1, 4, 9})<br /> std::cout << i << ' ';<br /><br />I feel like your example on page 2 lines 9-15 is very similar to the most vexing parse. In either case, you can use the {} syntax as a workaround. Using {} in this way feels very hacky to me even though I know Bjarne favors it. I like that you distinguish between "braced initialization" as a syntactic construct and "uniform initialization" as a feature of that syntactic construct.Alex Howletthttps://www.blogger.com/profile/01043149701435832314noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-35434221683995571772014-04-07T08:57:11.286-07:002014-04-07T08:57:11.286-07:00@Alex Howlett: It depends on what you mean by &quo...@Alex Howlett: It depends on what you mean by "properly"? If you check for assignment to self in your assignment operator, that's a test that's not performed in a constructor, and hence the instructions executed for assignment are not the same as for construction. In general, assignment and construction for user-defined types are not the same, but for built-in types, they always are.<br /><br />As regards the most vexing parse, your view is that I should essentially eliminate lines 7-21?Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-2681009981118787562014-04-07T00:32:50.071-07:002014-04-07T00:32:50.071-07:00In Item 7, I like that you mention that the {} syn...In Item 7, I like that you mention that the {} syntax allows you to initialize an STL container with a set of values. In my mind, the key feature of braced initialization is that it allows the initialization syntax for C++ containers to have symmetry with C array/struct initialization syntax.<br /><br />Regarding the paragraph about the ambiguous nature of '=', what about going the other way? You mention that "for built-in types like int, the difference is academic." Correct me if I'm wrong, but shouldn't the same hold true for any class that properly implements the assignment operator?<br /><br />I agree with James Edwards that you probably don't need to describe the most vexing parse again. What about just quickly mentioning that {} syntax avoids the most vexing parse and then referencing the Effective STL item?Alex Howletthttps://www.blogger.com/profile/01043149701435832314noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-23683952842162421292014-04-04T16:36:10.635-07:002014-04-04T16:36:10.635-07:00@James Edwards: Funny you should mention the mater...@James Edwards: Funny you should mention the material on the ambiguous nature of "=". I added it based on somebody else's comment on a different blog post. I agree that it's a C++98 thing, but I also agree that many people continue to be unaware that "=" need not imply assignment, so I'm inclined to keep it. As usual, there's no way to keep everybody happy. If you know about the issue, my coverage is superfluous. If you don't, it's not.<br /><br />The material on the most vexing parse needs to be present in some form, because it's one of the arguments in favor of using braces, and I also think that people coming to C++ from other languages are especially susceptible to confusion over the sometimes-you-need-them-and-sometimes-you-don't nature of parentheses when creating objects. Having covered it in only 16 lines, I thought the treatment was pretty quick :-) Of course, the fact that I include such "quick" treatments is one of the things that make the current Item drafts too long and not as focused as perhaps they should be.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-88534571940214839672014-04-04T09:51:36.168-07:002014-04-04T09:51:36.168-07:00Obviously this is just an opinion, but lines 12-23...Obviously this is just an opinion, but lines 12-23 of page 1 could be written in the present tense and be in any of your previous Effective books. Line 18 will still cause confusion and I still get programmers shocked by it, but nothing in C++11 changes that, so it feels superfluous. But I did learn there's a "confusing mess" lobby, so that's good. As part of the setup, the mention of std containers is important, so some brief explanation of that problem still needs to be explained.<br /><br />Page 3, lines 6-21 I think is another example. It's a very nice explanation of a C++ 98 issue, but I think it waters down the meat. A quick illustration of the issue and how it's now solved seems better.<br /><br />I may not be the target audience, though I plan on buying the book, but it feels to me like too much time is spent looking back, and not enough looking forward. I would assume, but you know assume, that new programmers will do what I did with your first book, read the headline, apply it in practice, and re-read until I not only get it, but it has become intrinsic to my code and I can confidently break the "rules" knowing exactly why it's OK.<br /><br />Cheers.James Edwardsnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-87321153571061968762014-04-02T17:36:00.378-07:002014-04-02T17:36:00.378-07:00@James Edwards: Thanks for your comments. As it ha...@James Edwards: Thanks for your comments. As it happens, Item 5 has now been turned into an Item devoted to auto type deduction (including in contexts beyond variable declarations, e.g., function return types and lambda parameters) and been added to the type deduction chapter, so the Item on parentheses vs. braces (the posted sample Item 7) is the only place I discuss the special overloading resolution rules for constructors. IME, people find them counterintutive, so it's worth explaining.<br /><br />I'd be interested in examples of topics I explain that you consider too elementary. I'm happy to leave information out that people should know or can easily look up, but I also try to include background information on language features that, in my experience (or at least in my opinion), readers are unlikely to be familiar with. It's hard to know where to draw the line.<br /><br />Currently, lines 3-16 on page 4 are redundant with information in the Item on auto type deduction, so I could eliminate the examples (lines 11-16), but lines 3-7 introduce what's coming up (special constructor overloading resolution rules), and lines 7-10 comprise a cross-reference to auto type deduction that needs to remain in any case, so only about a quarter page is arguably superfluous, IMO. On the other hand, authors think all their words are precious, so maybe I'm just being blind to material that can go. As I said, I welcome examples.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-76278707444999065082014-04-02T15:51:13.281-07:002014-04-02T15:51:13.281-07:00I finished reading the piece on initializers and I...I finished reading the piece on initializers and I think there are two things making it far too long. I gave a talk at work on initialization and covered what yuo have and more and barely crammed it into 1 hour. You have an item on auto x = {5} and an item on curly brace initialization. you're right to not merge them, but I would suggest take all the references to initializer lists out of 7 completely and merge that with 5. 5 should then come after 7. In 7 you can put in a "However, read the next item for more."<br /><br />The other thing I notice is that you spend more time than usual for your series explaining what they are. What I've enjoyed about your series is you assume we are smart enough to know what language features are and if we don't we can look them up.<br /><br />I hope this helps. It looks like pretty good coverage otherwise.James Edwardsnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-53256207020601933732014-03-26T09:36:30.956-07:002014-03-26T09:36:30.956-07:00@Bert Rodiers: Regarding Item 25, I actually think...@Bert Rodiers: Regarding Item 25, I actually think that a default by-value capture mode is more dangerous, because it lulls you into thinking that your closures are self-contained, but uses of non-locals (including data members implicitly accessed via this) may continue to dangle. The problem with the this pointer is specific manifestation of a broader issue. <br /><br />Regarding Item 30, note that I'm referring to returning universal references, not objects. For objects, applying std::move in return statements is generally wrong (and applying std::forward always is), but for urefs, applying std::forward is generally correct (as is applying std::move to rrefs). It's important to distinguish between objects and references in this kind of discussion. In my Item, I address both.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-1865637677624050202014-03-26T04:01:51.253-07:002014-03-26T04:01:51.253-07:00Related to Item 30: Pass and return rvalue referen...Related to Item 30: Pass and return rvalue references via std::move, universal references via std::forward.<br /><br />Make sure that people don't get the wrong take-away. From "return rvalue references via std::move" people might get the impression that is a good practice to write return std::move(...). Often this is not, first of all because it is most of the time not necessary: move is done implicitly if local values are returned. Moreover, Return value optimization and Named NRO disabled when you write "return std::move(...)". See also Howard Hinnant's discussion on Stack Overflow: http://stackoverflow.com/a/4986802/1274850<br />Bert Rodiersnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-9139868208808237022014-03-26T03:43:29.606-07:002014-03-26T03:43:29.606-07:00Related to "Item 25: Avoid default capture mo...Related to "Item 25: Avoid default capture modes".<br /><br />Probably this refers to possible dangling references. This is not specific to default capture modes. Also when you explicitly capture "this" or capture local variables by reference and return the lambda somewhere you have similar problems. I would change the item to "don't let lambda expressions outlive the life-time of the captured variables" or "Be careful when capturing this (implicitly or explicitly)".Bert Rodiersnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-31104928338058744822014-03-26T03:41:17.095-07:002014-03-26T03:41:17.095-07:00This comment has been removed by the author.Berthttps://www.blogger.com/profile/04725519084790412514noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-64778603286252601422014-03-25T16:09:21.957-07:002014-03-25T16:09:21.957-07:00@Scott Meyers
There's no guarantee that C fun...@Scott Meyers<br /><br />There's no guarantee that C functions won't throw exceptions. glibc actually implements POSIX cancellation points with exceptions, so an exception can be thrown by fopen(), for example.<br /><br />Of course your question stands for code you really know won't throw. In my opinion the answer here hinges on whether or not the cost of identifying this code is really worth the benefits gained through the optimization.<br /><br />I don't have any real data to back this up, but I suspect that in practice the small optimizations enabled by using noexcept are usually not, on their own, sufficient to justify the cost, and that instead the additional benefits that obtain in the case of move/swap and cleanup code are usually required to justify it.<br /><br />---<br /><br />Jon Kalb does briefly address this in his talk. You can see what he says at 40:25 in the part 2 video on his site. To summarize, he thinks the risk of mistakes during future modifications is significant enough and the enabled optimizations probably aren't great enough that if he doesn't need noexcept then he'd rather not risk it.<br /><br />So, no new information, just a different judgment call on the relative values.<br /><br />---<br /><br />Here are a couple emails where compiler devs discuss performance implications relevant to noexcept:<br /><br />http://lists.cs.uiuc.edu/pipermail/cfe-dev/2013-February/027966.html<br /><br />http://lists.cs.uiuc.edu/pipermail/cfe-dev/2013-October/032636.htmlSethnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-57385285135690239182014-03-25T11:29:50.768-07:002014-03-25T11:29:50.768-07:00Fix to the previous post: A2(std::initializer_list...Fix to the previous post: A2(std::initializer_list<int>) {}Vladimirnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-74940601868856987662014-03-25T11:20:57.814-07:002014-03-25T11:20:57.814-07:00@Scott Meyers
I've repeated Seth's words f...@Scott Meyers<br />I've repeated Seth's words from "Under this suggestion passing a temporary to any constructor would be ambiguous with constructors that take an initializer list, because the temporary would use a second set of braces, just like initializer_list." but the words aren't quite precise. The next situation is implied (if I got everything right):<br /><br />struct A1<br />{<br /> A1(int, int, int) {} <br />};<br /><br />struct A2<br />{<br /> A2(A1) {}<br /> A2(std::initializer_list) {}<br />};<br /><br />int main()<br />{<br /> A2 a2{{1, 2, 3}}; // {1, 2, 3} is supposed to be a "temporary" of type A1<br />}Vladimirnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-25281485961893308822014-03-25T11:06:57.857-07:002014-03-25T11:06:57.857-07:00@Seth
>>The entire point ... class initializ...@Seth<br />>>The entire point ... class initializable using the same syntax as built-in arrays.<br />Which led to the current sad situation.<br /><br />>>The rules for developers I proposed above work with C++11 as it is and achieve the goal of a _single_ syntax<br />I'm not saying the rules are not true, I'm saying they have no semantic motivation and exist only because of the syntactic quirks.<br /><br />>>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2532.pdf<br />I did read this particular paper (and a couple of others, but not all the sequence) but not especially carefully. I've reread it today.<br /><br />Meanwhile I actually found flaws in my own reasonings. But I still think the current rules are flawed too. Hm, should probably investigate more. Thanks for the discussion.<br /><br />@Scott Meyers<br />Sorry for spamming your blog with our prolonged conversationVladimirnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-91916538618561634082014-03-25T09:48:28.382-07:002014-03-25T09:48:28.382-07:00@Vladimir: Can you please elaborate on what you me...@Vladimir: Can you please elaborate on what you mean when you say that under current rules, passing a temporary to a constructor conflicts with il constructor? I'm not familiar with any conflict that arise only for rvalue arguments.<br /><br />Thanks.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-27917670028830223752014-03-25T09:10:19.408-07:002014-03-25T09:10:19.408-07:00@Jon Kalb: I took a look at your ESC slides, and I...@Jon Kalb: I took a look at your ESC slides, and I was taken aback by what appears to be your recommendation to restrict noexcept to move and swap functions. I'd be interested to know the technical basis for this recommendation. For example, if I have a non-inline function that uses only the C subset of C++ and that I believe will continue to be implemented in that fashion, why would I want to give up the optimizations in callers that using noexcept could enable?Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-40687361364581815692014-03-25T08:36:42.950-07:002014-03-25T08:36:42.950-07:00@Vladimir
>>> And that is good, because i...@Vladimir<br />>>> And that is good, because initialization with il constructor and aggregate initialization are absolutely different things.<br /><br />The entire point of introducing initializer_list was to make it so users could make their class initializable using the same syntax as built-in arrays.<br /><br />Furthermore one of the goals of uniform initialization in general was to use the same syntax with both aggregates and constructors.<br /><br />The rules for developers I proposed above work with C++11 as it is and achieve the goal of a _single_ syntax.<br /><br />If you're interested and haven't already read it, you might take a look at Bjarne's paper:<br /><br />http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2532.pdf<br /><br />That same year also has a number of other papers following the development on initializer lists.Sethnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-77818767750515037402014-03-25T02:07:56.104-07:002014-03-25T02:07:56.104-07:00@Seth
Small addition:
Under current rules passing ...@Seth<br />Small addition:<br />Under current rules passing a temporary to a constructor conflicts with il constructor too (with the same outcome as in the hypothetical situation - il constructor is selected)Vladimirnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-85936666234017770792014-03-25T01:36:54.463-07:002014-03-25T01:36:54.463-07:00@Seth
>>No, this suggestion just shuffles ar...@Seth<br />>>No, this suggestion just shuffles around which constructors are called by which syntax without solving the problem..<br />The problem is the syntactic conflict between initializer_list (il) constructors and non-initializer_list (n-il) constructors.<br />To shuffle things around is to make vector{1, 2} call n-il constructor, but keep vector{1, 2, 3, 4, 5} well formed and calling il constructor. My suggestion (not only mine actually, it's a relatively common suggestion) was to make vector{1, 2, 3, 4, 5} ill-formed, thereby eliminating the syntactic conflict completely.<br /><br />>>For another thing, initializing a vector under this suggestion is not uniform with initializing an array.<br />And that is good, because initialization with il constructor and aggregate initialization are absolutely different things. (Nonetheless, std::array can be initialized with double braced syntax too, just as std::vector.) Even if it's not good from someone's point of view, it's a small price compared to uniform initialization usable everywhere without syntactic conflicts.<br /><br />>>Under this suggestion passing a temporary to any constructor would be ambiguous with constructors that take an initializer list, because the temporary would use a second set of braces, just like initializer_list.<br />Well, that's a real issue. I can't quickly come up with a solution, probably some additional parenthesizing of the initializer could be allowed for such an emergency situations. But I again can argue that it's much lesser issue compared to, for example, using braced initialization in templates.<br /><br />Anyway, it's useless to argue now, because nothing can be changed (but I'm still interested :))Vladimirnoreply@blogger.com