tag:blogger.com,1999:blog-7101933101966798446.post4224086498686243729..comments2024-03-17T08:14:57.577-07:00Comments on The View from Aristeia: Thoughts on the Vagaries of C++ Initialization Scott Meyershttp://www.blogger.com/profile/05280964633768289328noreply@blogger.comBlogger43125tag:blogger.com,1999:blog-7101933101966798446.post-89037190762449513542015-09-27T09:51:34.001-07:002015-09-27T09:51:34.001-07:00@Andreas Weis: There is no need to create a genera...@Andreas Weis: There is no need to create a general rule regarding auto and braced initializers in order to have range-based for loops be able to iterate over braced initializers. All you have to do is create a special rule that applies only to range-based fors. Range-based for loops already have special treatment for arrays, so special treatment for a braced initializer would not be breaking new ground. Furthermore, such an approach would avoid the surprising behavior and inconsistency that I mention in the blog post.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-50326789382019242202015-09-27T05:21:22.290-07:002015-09-27T05:21:22.290-07:00One use case that I can see for auto deducing std:...One use case that I can see for auto deducing std::initializer_list from braced initializers is that you can do this:<br /><br />for(auto const& i : {1, 2, 3}) {<br /> ...<br />}<br /><br />Since std::initializer_list provides non-member begin() and end(), this is a very handy way of specifying an ad-hoc range of stuff to iterate over.<br /><br />Whether this single use case is worth all the confusion is of course debatable.Andreas Weishttp://pageant.ghulbus.eu/noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-10845665540672645542015-09-23T00:24:33.472-07:002015-09-23T00:24:33.472-07:00I think the type deduction for auto where no type ...I think the type deduction for auto where no type is specified (as return value of a function or explicit) should be ill-formed and an error from the compiler.<br />That is bad design and leaves the reader with too much confusion about the type and the developer can avoid to express his intentions (see CppCoreGuidelines Philosophy P1 and P3).<br /><br />When you look at the designs, the double brace is more clear and would have avoided the problems with vector (in its current design) and copy list initialization.<br />The current design was choosen that you can initialise a plain array and a std::vector with the same syntax with only 1 curly braces:<br /><br />int array[5] = { 1, 2 };<br />std::vector<int> vector = { 1, 2 };<br /><br />And thats what leads to the problems with the size + value constructors from vector now, when you direct list initialize a vector.<br /><br />But somewhere else i read a post that the design of the vector contructors should be changed (which is also hard with backward compatibility). I cant remember the details but they had good points there.<br /><br />And this is really a problem when you work with template code T{1,2} where you dont know what T is and what constructors T has. There i think T{{1,2}} whould have also been a better design and intent what you are trying to do.Andreas Quastnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-8998614763333366472015-09-15T14:44:08.084-07:002015-09-15T14:44:08.084-07:00Thanks for redirecting me to Item 7. I had quickl...Thanks for redirecting me to Item 7. I had quickly skimmed the table of contents, but apparently missed that item ;-) I was quite sure there were things I overlooked. Thanks for pointing them out.Bart Vandewoestynehttps://www.blogger.com/profile/15864107848559392386noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-25578444023761287472015-09-15T12:32:09.263-07:002015-09-15T12:32:09.263-07:00@Bart Vandewoestyne: There are problems with tryin...@Bart Vandewoestyne: There are problems with trying to use only direct initialization. To detect narrowing conversions or to initialize aggregates (e.g., structs and arrays), you have to use list initialization. Direct initialization syntax isn't valid for default member initializers. Also, direct initialization syntax can lead to the most vexing parse, which list initialization avoids.<br /><br />I do my best to cover this territory in Item 7 of <i>Effective Modern C++</i>.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-38230550423778039202015-09-15T12:12:03.095-07:002015-09-15T12:12:03.095-07:00So with direct initialization, we get no errors if...So with direct initialization, we get no errors if we change from int to <br />std::atomic, and using direct initialization with auto does not change the type to std::initializer_list.<br /><br />That makes me wonder whether a good guideline would be "Always use direct<br />initialization." Probably I'm overlooking a lot of things, but from a learning<br />perspective, I would be happy to hear comments on this line of thought.Bart Vandewoestynehttps://www.blogger.com/profile/15864107848559392386noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-87186403158521226562015-09-15T09:22:25.900-07:002015-09-15T09:22:25.900-07:00@Aaron McDaid: Interestingly, you can use double b...@Aaron McDaid: Interestingly, you can use double braces to distinguish invoking a constructor with zero arguments (one set of braces) versus invoking a constructor with an empty initializer list (as in w5 below):<br /><br />class Widget {<br />public:<br /> Widget();<br /> Widget(std::initializer_list il);<br />}; <br /><br />Widget w1; // calls default ctor<br />Widget w2{}; // also calls default ctor<br />Widget w3(); // most vexing parse! declares a function!<br />Widget w4({}); // calls std::initializer_list ctor with empty list<br />Widget w5{{}}; // dittoScott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-53484320665595730362015-09-15T08:24:46.187-07:002015-09-15T08:24:46.187-07:00@Mark Atkinson, @Scott Meyers,
The question of us...@Mark Atkinson, @Scott Meyers,<br /><br />The question of using double braces for lists is very interesting, because it highlights what is so strange about the status quo. In my opinion, every brace or parenthesis should do one thing, and do it well.<br /><br />For example, we're well used to the idea of parentheses to group constructor arguments.<br /><br /> int x(0); // parentheses around the constructor args.<br /><br />Imagine taking c++03 and adding support for initializer lists. Also, imagine a constructor that takes two arguments, a string and a list of ints:<br /><br /> MyType y("primes", {2,3,5,7});<br /><br />Now a parallel proposal which allows braces to be used instead of parentheses around constructor args:<br /><br /> int x{0};<br /><br />Bringing these two (unrelated) proposals together allows:<br /><br /> MyType y{"primes", {2,3,5,7};<br /><br />Here, each brace does one thing and does it well. It eithers groups the elements of a list, or a collection of arguments for a constructor. So far so good.<br /><br />Imagine now that MyType has a constructor that takes one arg, which happens to be a list. i.e. the string is not needed. The following code is very readable to me, braces for lists and parentheses around the constructor args:<br /><br /> MyType y( {2,3,5,7} );<br /><br />Combined with the other proposal we have<br /><br /> MyType y{ {2,3,5,7} };<br /><br />I wish the committee had stopped here. Most of them problems would go away. There were two, unrelated, proposals that play nicely together.<br /><br />The problem is that the committee went further. As well as adopting these two proposals, they added an extra "twist" that allows developers to drop one set of braces where one set (the list braces) are nested directly inside the other set (the constructor-arg braces). In other words, the compiler will (sometimes) infer an extra set of braces, leading to the problem that some vector constructors are inaccessible if you want to use "uniform" initialization.<br /><br />I guess it's too late to undo the "twist", due to backwards compatibility, but I do hope that it should be possible to fix all this somehow.<br /><br />(I must admit that I have zero knowledge of the actual history of the proposals, but the (fake) story I've told above helps me to understand the current rules.)Anonymoushttps://www.blogger.com/profile/17204472992759448532noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-8187481308614257482015-09-14T23:43:06.006-07:002015-09-14T23:43:06.006-07:00This "copy initialization" is misleading...This "copy initialization" is misleading especially when dealing with move-only types.<br /><br />auto x = std::unique_ptr{new int}; // ok<br />auto y = x; // error! copy-constructor is deletedAnonymoushttps://www.blogger.com/profile/12803162995307824344noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-71209122593238507672015-09-14T10:48:24.062-07:002015-09-14T10:48:24.062-07:00The undocumented incompatibilty with C++14 is an o...The undocumented incompatibilty with C++14 is an open Core issue (http://wg21.link/cwg2038) and wording should appear in Annex C in the next standard.Unknownhttps://www.blogger.com/profile/12701421274845889905noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-6469536000366243082015-09-09T19:37:16.090-07:002015-09-09T19:37:16.090-07:00Also note that if you have a default constructor, ...Also note that if you have a default constructor, direct initialization, without any parameters(which I suppose, isn't actually direct initialization), will cause C++ to read a function deceleration- not a variable instantiation. This is another gotcha that seems to get a lot people.<br /><br />Perhaps good rule of thumb should be: "use direct list initialization as your default method of initialization". I've always referred to it simply as brace initialization.Trevor Hickeyhttp://stackoverflow.com/users/908939/trevor-hickeynoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-22858778489805090572015-09-09T12:57:40.026-07:002015-09-09T12:57:40.026-07:00@Scott:
"Let's assume that it's wort...@Scott:<br /><br />"Let's assume that it's worthwhile to distinguish the concepts of direct and copy initialization, i.e., to distinguish between initialization contexts where explicit conversions are and are not permitted. That says nothing about whether those concepts should be deferentially applied in the syntactic constructs I wrote about."<br /><br />In my example, I am trying to show that when you are declaring variable that needs to be initialized with some expression, you should have ability to decide whether you want to allow explicit conversion. So if we assume that distinction is worthwhile, we accept the fact that we need two initialization syntax (I will not argue, how they should look).<br /><br />"As somebody with a lot of experience trying to explain to programmers that [...] do the same thing and typically generate the same code if both compile (and that the latter construct has nothing to do with assignment), I'm inclined to think that this would be a net win." <br /><br />The whole point lies in the line "if they both compile". The value in the differentiation is that the code will not compile after change. This is the same as explaining that:<br />double a = 2, b = 5;<br />double c = 2 + 5;<br />Generate the same (in terms of performance, used memory code) as:<br />std::chrono::duration a = 2, b = 5;<br />std::chrono::duration c = 2 + 5;<br />My point is that we should not try to hide the difference between direct and non-direct initialization syntax, by embrace them and describe when they are actually useful.<br /><br />For my answers:<br />Q1: It is necessary to have two ways to initialize, and except the simple cases, they do not do the same thing. I agree that fact that the {} behaves differently than () in initialization (vector with size constructor case) is bad design.<br /><br />Q2: The fact that you cannot copy-initialize non-copyable atomic seems reasonable. The fact that you can copy-list-initialize them is insane.<br /><br />I think that the intent of $13.3.1.7 was to made following ill-formed:<br />struct A<br />{<br /> A(int);<br /> explicit A(double);<br />};<br />A a = 10.0; //will call A(int);<br />A a = {10.0}; //ill-formed<br />Not to remove requirement of accessible copy-constructor.<br /><br />Q3: The whole paper was done to make generalized initializers works. Using in capture [x{10}] is equivalent to auto x{10} and declared initializer list. The current resolution is compromise that makes lambda work. We still should say that auto + {} is a problem and avoid that. <br /><br />I think that the original initializer_list design is not as good as it should. Having separate syntax that will force creation of initializer_list and no special rules for deduction will be more pragmaticall. There are more problem with initializer_list itself: (https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/cYcqxtsyH2c).tkaminhttps://www.blogger.com/profile/07264151813971229104noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-41546438441795640392015-09-09T11:52:45.487-07:002015-09-09T11:52:45.487-07:00@tkamin: Let's assume that it's worthwhile...@tkamin: Let's assume that it's worthwhile to distinguish the concepts of direct and copy initialization, i.e., to distinguish between initialization contexts where explicit conversions are and are not permitted. That says nothing about whether those concepts should be deferentially applied in the syntactic constructs I wrote about. For example, the syntax<br /><br /> T var = expr;<br /><br />is currently defined to be copy initialization, but it could just as easily be defined to be direct initialization. As somebody with a lot of experience trying to explain to programmers that<br /><br /> T var(expr);<br /><br />and<br /><br /> T var = expr;<br /><br />do the same thing and typically generate the same code if both compile (and that the latter construct has nothing to do with assignment), I'm inclined to think that this would be a net win. <br /><br />Getting back to the fundamental question, do you think the examples I posted represent good language design?Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-29050402784863889972015-09-09T10:57:13.008-07:002015-09-09T10:57:13.008-07:00The pair should have std::chrono::seconds and int ...The pair should have std::chrono::seconds and int as the template arguments.tkaminhttps://www.blogger.com/profile/07264151813971229104noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-45515205445489127682015-09-09T10:54:12.190-07:002015-09-09T10:54:12.190-07:00@Scott Meyers:
What differentiates copy and direc...@Scott Meyers:<br /><br />What differentiates copy and direct initialization, is that the later allows the explicit constructor to be called. The N4387 introduces conditional explict specification for the pair/tuple constructors from elements/other tuples. The idea is that the constructor will be explicit if at least one of the element requires conversion by direct-initialization<br /><br />As consequence following:<br />std::pair p1 = {get_timeout(), get_retries};<br />std::pair p2 = get_timeout_and_retires();<br />Will compile successfully if get_timeout return compatible time unit, the same applies for first element of tuple-like type returned from get_timeout_and_retires().<br /><br />This would be excatly the same as for:<br />void foo(std::pair);<br />foo({get_timeout(), get_retries});<br />foo(get_timeout_and_retires());<br /><br />The direct initialization is mechanism that allow you to perform the same checks during the compilation, for return from function to caller, like the ones used for passing arguments to function.tkaminhttps://www.blogger.com/profile/07264151813971229104noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-902703524851301802015-09-09T10:38:21.067-07:002015-09-09T10:38:21.067-07:00@tkamin: There is no mention of list initializatio...@tkamin: There is no mention of list initialization in N4387. Can you please clarify what you mean?Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-69093515836012950052015-09-09T10:17:40.631-07:002015-09-09T10:17:40.631-07:00The difference between direct and copy initializat...The difference between direct and copy initialization is the tool at the hand of the programmer that can express intent of the code and prevent future errors. In the cases when we initializers has a type and value visible at the point of declaration it may look superfluous. But if we consider initialization from the result of the function, we can use direct/copy initialization to express our intent.<br /><br />For example:<br />std::chrono::seconds s = get_timeout();<br />//In that case I will get a compilation error if timeout will return bare number (without unit) or I will get loosy conversion<br /><br />std::chrono::seconds s(get_timeout());<br />//I am one that is assuring that timeout is returning seconds and responsible for the errors if it start to return bare number of milliseconds.<br /><br />Using the auto-everywhere syntax actually prevents us for writing the statement in the that will guarantee that unit mismatch produces compile time error:<br />auto s = get_timeout(); //Will note declare seconds<br />And using ETII:<br />auto s = std::chrono::seconds(get_timeout()); //Will work even if timeout is returning bare number (unknown unit).<br /><br />Saying that direct and copy initialization should work the same, is like saying that the:<br />foo(new T());<br />Should work even if foo is accepting shared_ptr/unique_ptr. Its effectively removes the ability to differentiate safe and unsafe conversion.<br /><br />For the explanation of need of differentiation between direct and copy list initialization I recommend reading: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4387.html (voted in C++17).tkaminhttps://www.blogger.com/profile/07264151813971229104noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-79921431341439510002015-09-09T00:56:22.859-07:002015-09-09T00:56:22.859-07:00@Scott There is no mention of n3922 in n4458 becau...@Scott There is no mention of n3922 in n4458 because it wasn't raised as a core language issue, but came in directly via the Evolution Working Group (it's issue 161 in <a href="http://open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4540.html#161" rel="nofollow">n4540</a>)Anonymousnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-88833900550135469802015-09-08T20:37:22.943-07:002015-09-08T20:37:22.943-07:00@Paul Jurczak: Right you are. Fixed, thanks :-)@Paul Jurczak: Right you are. Fixed, thanks :-)Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-56813001722373070332015-09-08T19:49:31.316-07:002015-09-08T19:49:31.316-07:00Hazardous materials lawyer with pedantic bit set w...Hazardous materials lawyer with pedantic bit set would write "hazmat suit" instead of "hazmet suit" ;-)Paul Jurczakhttps://www.blogger.com/profile/03362995325160390177noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-10707859711983690532015-09-08T12:39:23.989-07:002015-09-08T12:39:23.989-07:00I would prefer if
x = {...}
always attempted to ...I would prefer if<br /><br />x = {...}<br /><br />always attempted to use std::initializer_list (typically to do something like aggregate initialization), while<br /><br />x{...}<br /><br />always attempted to call a constructor (ie, the same as `x(...)`, but without narrowing), regardless of the type specified. It really sucks that, currently, whether `x{...}` calls a constructor or std::initializer_list is not immediately apparent (you have to know whether the type has a std::initializer_list constructor).<br /><br />IMHO, it was a pretty big mistake in C++11 to 'overload' on the syntax of `x{...}` for these two things.Anonymousnoreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-37862579718361688262015-09-08T12:37:28.857-07:002015-09-08T12:37:28.857-07:00This comment has been removed by the author.Kevinhttps://www.blogger.com/profile/12150961553069532055noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-31445658579513242142015-09-08T10:09:33.736-07:002015-09-08T10:09:33.736-07:00@Mark Atkinson: I'm not convinced that we need...@Mark Atkinson: I'm not convinced that we need a syntactic shorthand for std::initializer_list objects. In my heart of hearts, I'd like to agree with Chris Glover (above) that such lists just don't occur in real code often enough to worry about. Unfortunately, I know that several people tried to come up with C++98 constructs to serve the purpose (typically by declaring a special type and then overloading the comma operator), and there was even a Boost library that did just that. So there seems to be a demand for the idea, possibly because it's useful in small test programs, demos, and unit test code.<br /><br />As for using double curly braces to indicate an initializer list, I haven't given it much thought. One concern that comes to mind is that brace initialization can be nested (e.g., for aggregates), so double curly braces already has a meaning in some contexts. Whether that would be problematic in developing a proposal for a double-curly-brace-based initializer lists, I don't know.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-14619153809910955352015-09-08T09:57:35.604-07:002015-09-08T09:57:35.604-07:00I would also like "auto x = { ... }" be ...I would also like "auto x = { ... }" be ill-formed by the reason that no type can be determined (just like with function template arguments). And I would also like automatic return type deduction to use the same principle, so that this can be written:<br /><br />auto f() {<br /> if(...) {<br /> std::vector f; <br /> return f;<br /> }<br /> return {};<br />}<br /><br />Currently ill-formed because the "{}" would deduce to initializer_list which is nonsense for functions to return. If we make the rules the same as for "auto x = ..." and do not deduce that as initializer_list, it would just work and the "return {}" would be treated as a non-deduced context and be well-formed.Anonymoushttps://www.blogger.com/profile/03650482358271835288noreply@blogger.comtag:blogger.com,1999:blog-7101933101966798446.post-37222172978443785642015-09-08T09:36:31.180-07:002015-09-08T09:36:31.180-07:00@cmeerw: There's no mention of N3922 in N4458,...@cmeerw: There's no mention of N3922 in <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4458.html" rel="nofollow">N4458</a>, which seems to be the most recent document summarizing core language defects. Note that even if the issue were listed there, the document points out that "[Issues listed here] should not be considered definitive until or unless they appear in an approved Technical Corrigendum or revised International Standard for C++." To me, it's clear that in C++11 and C++14, an auto-declared variable using direct-list-initialization is of type std::initializer_list.<br /><br />Not that this makes much difference in practice. From what I can tell, there is no way to get the current compilers from Gnu and Microsoft to behave as C++14 dictates. Both appear to unconditionally implement the N3922 behavior.Scott Meyershttps://www.blogger.com/profile/05280964633768289328noreply@blogger.com