Tuesday, January 29, 2013

Effective C++11: Content and Status

Before I summarize the Items I currently plan to include in Effective C++11 ("EC++11"), I'd like to explain a little about how I come up with the guidelines in my books.

It'd be sweet to imagine that I develop all the guidelines myself. If you're very technical about it, I do. I come up with all my own Item wordings, and I write all my own Item justifications. But the ideas behind the guidelines have generally already fought their way to acceptance in the C++ community. It's this acceptance that gives me confidence that the advice in the guidelines is both accurate and useful. My Effective books strive to summarize practices that are known to be effective, not to introduce new practices I hope will be.

In the case of C++11, the Standard is only about 18 months old, and some parts of it are yet to be implemented by major compilers. The experience with C++11 that is necessary to identify useful advice is therefore limited. But even the features that have not seen widespread use have been discussed and debated for years, and sometimes that gives rise to "observations" about those features that seem likely to be important. Consider, for example, what happens in a destructor for a std::future. If the future came from std::async, the future's destructor blocks until the asynchronously running thread completes. If the future did not come from std::async, the future's destructor doesn't block. That's already interesting, but, in my mind, it becomes even more interesting when combined with the observation that the destructor for a "joinable" std::thread calls the terminate function.  So we have three different behaviors for a single high-level concept: destruction of the object "responsible" for another thread.

I think this divergent behavior is likely to trip people up, and that gets it on my candidate list for EC++11. But what is the Item?  What is the advice? I could go with the lame-o "Understand destructor behavior in std::future and std::thread," but that's just cloaking an excuse to describe language rules in guideline form. I'm not above doing that when I can't think of anything better, but I prefer to find a way to offer advice that could be checked by a tool or in a code review. "Understand" rules fail that test.

(As an aside, the behavior of the destructors for std::futures produced by std::async is controversial and may change in the next revision of C++ (currently forecast to be in 2014). Some implementations already deviate from the behavior dictated by the Standard. That muddies the water yet further, but that's a problem for me and my book, not this blog post.)

You can think of "observations" about C++11 as proto-Items. They're not yet in guideline form, but they seem important enough to justify trying to find a way to work them into the book. Whether they'll make the book, and, if so, whether they'll do it as a standalone Item or as a side-discussion in another Item, I don't yet know.

At last year's C++ and Beyond, I gave a talk entitled "Initial Thoughts on Effective C++11." It had my usual guideline format. I also gave a talk on "Secrets of the C++11 Threading API," which consisted of observations about C++11's threading support.The material in those talks, combined with the feedback I got from giving them and mixed in with my experience explaining the idea of universal references, ultimately yielded the initial list of candiate Items for EC++11. The current snapshot of my vision for Effective C++11 is:

Moving from C++98 to C++11

  • Prefer auto to explicit type declarations.
  • Remember that auto + { expr }std::initializer_list.
  • Prefer nullptr to 0 and NULL.
  • Prefer enum classes to enums.
  • Prefer alias templates to typedefs.
  • Declare overriding functions override.
  • Distinguish () and {} when creating objects.
  • Prefer emplacement to insertion.
  • Declare functions noexcept whenever possible.
  • Make const member functions thread-safe.
  • Avoid std::enable_if in function signatures.
  • Handle iterators where copying means moving.

Rvalue References, Move Semantics, and Perfect Forwarding

  • Distinguish universal references from rvalue references.
  • Avoid overloading on universal references.
  • Pass and return rvalue references via std::move, universal references via std::forward.
  • Assume that move operations are not present, not cheap, and not used.
  • Be aware of perfect forwarding failure cases. 
  • Understand reference collapsing.

Secrets of the C++11 Threading API

  • Thread construction may throw.
  • Destroying a joinable thread calls terminate.
  • Arguments passed to std::thread, std::async, and std::call_once are unconditionally copied.
  • std::async is really two different functions with somewhat different APIs.
  • Futures from std::async are special.
  • void futures can be used for interthread communication.
  • To poll a future, use wait_for with a zero timeout.
  • Native handles let you go beyond the C++11 API.
  • Clock adjustments affect _until functions.

Lambda Expressions

  • Prefer lambdas to std::bind.
  • Prefer lambdas to variadic arguments for threading functions
  • Beware default captures in member functions.

Smart Pointers

  • Use std::make_shared whenever possible.
  • Prefer pass-by-ref-to-const to pass-by-value for std::shared_ptrs.

Miscellaneous

  • Pass by value when you’ll copy your parameter.
  • Keep abreast of standardization developments.
The number of Items on this list, the wording they have, the order in which they will occur, and whether they will ultimately be present are not just subject to change, they will change. This is especially true for the material pertaining to the threading API, because those "Items" are still just observations. But that's what my draft TOC looks like right now.

I also have a set of candidate guidelines that I currently feel are less important and hence less likely to make the book. This list will also change over time, but for your voyeuristic pleasure, this is what it contains:
  • Prefer non-member begin/end to member versions.
  • Declare std::thread and std::future members last.
  • For copyable types, view move as an optimization of copy.
  • Understand decltype.
  • Use std::compare_exchange_weak in loops.
If you have comments regarding any of the potential guidelines above, or if you have suggestions about what's not above but you believe should be, feel free to let me know, either as comments on the blog or via email: smeyers@aristeia.com.

"How much of this book have you written?," you may be wondering. None. Not a word. Zero percent. I haven't started writing, in part because I've allowed myself to get distracted by things like, um, creating blog entries...

But wording is everything. If you change the question to "How much of the work required to write this book have you already done?," the answer changes.  When I announced the existence of my annotated training materials for C++11, I explained that my approach to writing a book consists of three steps:
  1. Master the material.
  2. Figure out what "story" I want to tell, i.e., what to cover, what to omit, what order to cover things in, what examples to use, etc.
  3. Write it up.
I went on to break step 2 down as follows:
  • 2a: Come up with a story that I think will work, i.e., that will effectively convey the technical information.
  • 2b: Develop a training course corresponding to that story.
  • 2c: Deliver the training course to professional developers and see how well the story works. In places where it doesn't work as well as it should, return to step 2a and iterate until everything is satisfactory.
 For EC++11, I've pretty much completed steps 1 and 2, so from that perspective, I'm two-thirds done with the book :-) In theory, I know what I need to say. I just need to write it down. In reality, that understates the difficulty of the work that's still to be done, but to some degree, what remains is an IO-bound operation.

I would hope it goes without saying that you can't have too many iterations of steps 2a-2b-2c. Every time I present the material, I learn things about how I can do a better job. Sometimes it's about something I've said that was confusing or unnecessary. Sometimes it's about something I didn't say, but should have. Sometimes it's about a relationship between what I'm discussing and another topic that seemed completely independent to me, but didn't to the people I was working with. To that end, I'll be making several presentations of a new training course called Effective C++11 Programming several times this year. The first is slated to take place in June. I'll post details soon.

For the next few months, I'm doing my best to keep my calendar free. I need time to write Effective C++11. You know how slow IO-bound operations can be.

Scott

37 comments:

  1. Hi Scott, I see you're now recommending using auto rather than explicit type declarations. At C&B when Herb presented this tip you disagreed. I'm curious as to what changed you mind.

    ReplyDelete
  2. @Tom: I haven't really changed my mind. I just don't agree with the particular way Herb makes the recommendation.

    Herb and I agree that “auto” is a great addition to the language. Even Herb doesn’t really advise people to always use auto, because he points out that there are situations where it doesn’t do what you expect. My concern is that some of those situations are very difficult to anticipate, because they require knowledge of how things are implemented. It’s only by knowing that operator[] for vector<bool> returns a proxy, for example, that you can understand that

    std::vector<bool> vb;
    ...
    auto b = vb[1];

    can get you into trouble. (The type of b is not bool, it’s std::vector<bool>::reference.) In cases where you lack the knowledge of implementation details that would allow you to understand that “auto” won’t do what you expect, following the advice to blindly use “auto” is a mistake, IMO.

    My tentative advice in this area is “Prefer auto to Explicit Type Declarations.” That’s just a tad more conservative than Herb’s “Always...unless” advice, but I think it’s important to acknowledge up front that not only are there cases where “auto” won’t be correct (Herb and I agree there), but that it is, in general, impossible to anticipate all those cases in advance.

    Scott

    ReplyDelete
  3. Hi Scott,

    When auto came available in VC10, I immediately started it using everywhere. Especially during the initial design and development phase, refactoring becomes so much easier.

    The only places where it tripped me up are simple for loops where `(auto i = 0; i < container.size(); ++i)` gives signed/unsigned warnings. But those kind of loops are rare now with the new range-for stuff.

    The non-generic behavior of vector of bool should have been fixed by now by renaming it to bitvector. IMO, keeping this pitfall in the Standard Library is the error here, not the "blind" use of auto. Maybe a new Item "Make sure generic code behaves generically" would help? :-)

    Which other surprising pitfalls have you come across?

    ReplyDelete
  4. Scott,

    Just a wild thought: have you ever considered putting draft chapters online while you are writing, to get feedback from the community before everything actually gets published?

    ReplyDelete
  5. > auto

    Probably everything has been said already. But not on this thread! :)

    Use of "auto" reduces readability of code, except for code in which types do not convey much information to the reader, like in templates.

    In other cases it's important for the user to know if he is dealing with a std::vector or a std::list and not infer that from how it is used, or worse, have to mouse-hover over each variable (while this is not even possible for most editors!).

    Considering that code is read more often than it is written, optimizing for "writing convenience" is a bad trade-off when giving away readability.
    "auto" should only be used when it improves readability - which is contrary to Herb's advice - not the case most of the time. This is probably the single item I (highly) disagree with Herb.

    ReplyDelete
  6. The biggest hole here is unique_ptr. Many, many types in C++03 could be entirely discarded in favour of unique_ptr, and the deprecation of the Rule of Three is also an essential topic.

    Finally, user code should virtually never return rvalue references.

    ReplyDelete
  7. @Rein Halbersma: Probably the most common pitfall regarding auto is the one involving its use with brace initialization:

    auto i = { 10 };

    This declares an object of type std::initializer_list<int>, not an int.

    Scott

    ReplyDelete
  8. @Bart Vandewoestyne: My publisher and I have discussed not just making draft chapters available for public comment, but also draft Items. No decision has been made about whether we'll do this.

    Scott

    ReplyDelete
  9. @DeadMG: Do you have some proposed advise regarding the use of unique_ptr? I think the general topic is covered in Effective C++'s Item 13 ("Use objects to manage resources"), and something like "Use std::unique_ptr for owning pointers" doesn't seem to offer any insight.

    Scott

    ReplyDelete
  10. @Zenju: I believe there are solid technical reasons to prefer auto most of the time, but I'm not going to try to summarize them here. I will say that use of auto is optional, so if you believe that it reduces the clarity of the code you write, don't use it. At some point I'll write up my take on auto, and when that happens, you'll get a chance to see it and see if you find any of my arguments compelling. We can fight it out then :-)

    Scott

    ReplyDelete
  11. I'm curious why you have an item "prefer lambdas to std::bind"? They appear to have the same performance and std::bind seems to be as clear (or more clear) about what you are trying to do.

    Is there a specific reason you believe that lambdas are better than std::bind?

    ReplyDelete
  12. "make_shared whenever possible", but also, let's not forget the caveat with weak_ptr's that, solely, can hold memory deallocations because of make_shared.

    ReplyDelete
  13. One item I would like to see is "Prefer returning unique_ptr's rather than shared_ptr's from functions." A unique_ptr can easily be converted to a shared_ptr or a user-defined smart pointer type. Since a shared_ptr does not have a release() method, it cannot be converted into any other smart pointer type. This means that functions returning a unique_ptr are much more flexible than those returning a shared_ptr.

    ReplyDelete
  14. Regarding the unique_ptr thing, it sounds like there would be value to making a new edition of EC++ "Volume One" that still has the same items, but updated for C++11.

    ReplyDelete
  15. @spott: Let me begin by saying that I was naive in thinking I could post candidate Items without being asked to justify them, but that's what I was thinking. Silly me. So my policy, effective immediately, is to ask people to understand that if I get drawn into discussions of the bases for my Items online, I'll never have time to write the book. Since you asked before the policy went into effect, however, you get grandfathered in :-)

    Neither lambdas nor std::bind is strictly more powerful than the other. They have differing strengths and weaknesses. Lambdas, for example, can easily refer to overloaded function names, while std::bind requires that the function it binds have its name, if overloaded, cast to its type, which is ugly and leads to truly horrific error messages if you forget to do it. On the other hand, std::bind can bind move-only types, but lambdas can't.

    From my perspective, the big wins for lambdas are (1) explicitness of capture mode and (2) general readability.

    std::bind captures what it binds by value, and the only way to change that it to change the meaning of "by value" by passing what you want to capture through std::ref/std::cref. But you have to learn that things are always captured by value; there's no syntactic hint in the use of std::bind that tells you what's going on. With lambdas, there is no default capture mode. If you specify a default mode, it's there for everybody to see, and there's no need for std::ref/std::cref nonsense. My experience is that the syntax and semantics of std::bind is clear only to those who've taken the time to painfully learn what's going on under the covers. For people who have never seen or used std::bind, explaining what's happening is a challenge. The syntax for lambdas must also be learned, of course, but I've taught both, and my experience is that lambdas are much easier to teach.

    But the big win is readability. Which of the following do you think is clearer?

    auto f1 = [=](int x) { return x >= lb && x <= ub; };

    using namespace std::placeholders;
    auto f2 = std::bind(std::logical_and<bool>(),
    std::bind(std::greater_equal<int>(), _1, lb),
    std::bind(std::less_equal<int>(), _1, ub));

    There's more to say on the topic, but that's an overview of my arguments.

    Scott

    ReplyDelete
  16. @Francisco Lopes (regarding std::shared_ptr): Per my reply to spott, I plan to avoid discussing the justifications for my Items in this blog (that's what the book is for), but since you made the comment before I announced my policy, you're also grandfathered in :-)

    The weak_ptr caveat is only one of the reasons why you might want to avoid std::make_shared. Two others are that you want to specify a custom deleter or that you need fine-grained control over the layout of the object the std::shared_ptr will point to (e.g., you might want to ensure it's placed at the beginning of a cache line). Hence the "whenever possible" part of the Item wording.

    Scott

    ReplyDelete
  17. @Joe: Nice suggestion, thanks, though my initial inclination for wording is "Return std::unique_ptr from Factory Functions." Please send me your full name, because if this ends up in the book, I'll want to give you (along with Herb Sutter, who has advocated the same thing, now that I think of it) credit in the acks.

    Scott

    ReplyDelete
  18. @Rick Yorgason: I'm not sure what the path forward for EC++ is. Updating it for C++11 involves pretty much a complete rewrite, even if the Item titles remain essentially unchanged, so it would make sense to start pretty much from scratch. But once EC++11 is out, I'll want to avoid duplicating material between the books.

    I'm not going to worry about that now. One book at a time :-)

    Scott

    ReplyDelete
  19. Hello all. Just one comment: looking forward to the book. I find this kind of material very useful for teaching :) Ten months, right? Oh, no promises...

    ReplyDelete
  20. Firstly, always prefer unique_ptr to shared_ptr if possible, which should be a majority of the time. You can go from unique to shared, but not the other way around (unfortunately). In addition, unique_ptr is more efficient.

    Secondly, always implement deep-copying resource-owning classes in terms of unique_ptr. Unlike auto_ptr, the default semantics for unique_ptr are quite safe, and only copy semantics need implementing. Show that for a non-copyable resource-owning class, simply holding unique_ptr means no special members implemented, and for a deep-copy variety, then merely implementing the copy constructor and copy assignment is fine.

    Thirdly, show the flexibility of unique_ptr's destructor so that it can be used for, e.g., POSIX file descriptors and other non-pointer resources.

    Fourthly, show a value_ptr that deep copies, based on unique_ptr. Bonus points if correctly handling polymorphic types.

    Finally, show deferred-destructor idiom for incompletes.

    ReplyDelete
  21. I second DeadMG suggestions. It would be great to have a nice section on the finer points of unique_ptr.

    ReplyDelete
  22. "Firstly, always prefer unique_ptr to shared_ptr if possible, which should be a majority of the time."

    I believe that a more correct item title would be "In doubt, use std::unique_ptr", which would be a similar recommendation than "in doubt, use std::vector".

    I think this because the choice of smart pointer is bascially the decision of life-time strategy for that object, which means sometime you don't have a clear idea of what will really be needed in the end, a bit like when you have to chose a container and you're not sure exactly how you'll use it.

    In both case, to get faster to the point you have enough knowledge to decide what to chose, using a vector/unique_ptr by default allow you to not be stuck in analysis paralysis and is still efficient enough in case the code will not be touched later. Once you know that it's not the right container/smart pointer, you can easily change the related code without too much problems.

    I often have doubt about a lifetime strategy for some object in particular in design phases, so I use a unique ptr until I find that it's not enough.

    ReplyDelete
  23. Hello Scott,
    Will there be any topic on constexpr? Also I was thinking of "delete compiler generated functions if customers won't be using" but it's difficult to justify myself, unless there's a specific goal such as creating a non-copyable class. Would compiler take more time to compile a class with default implementation when compared to a class with some are deleted?

    Thanks
    Jeonggyu from Hillsboro, OR

    PS : I am a big fan of your books please don't forget that.

    ReplyDelete
  24. @Jeonggyu Lee: Funny you should mention constexpr, because I've been reading about it and thinking about it today. I have as a very tentative guideline, "Use constexpr whenever possible," but I have to do more research. Right now, I don't see any downside to declaring functions constexpr when (1) they can perform compile-time computations and (2) implementing them as a single statement doesn't so contort them that nobody can understand them. Similarly, declaring variables constexpr to force them to be statically initialized doesn't seem to have any drawbacks (for variables that can truly be statically initialized), but, again, I have to look into this a bit more.

    If you have a proposed guideline regarding how to use constexpr effectively, let me know!

    Scott

    ReplyDelete
  25. Regarding constexpr, I would like take the opportunity to make the following observation:

    Herb has expressed concern that meta-programming features such as constexpr will be over used and therefore build times will slow to a crawl. As build times for large projects can already be long, this is a valid concern.

    For this reason I would be very careful about attaching "whenever possible" to this feature.

    I am very keen to see constexpr implemented in VC++ and I think C++ needs to go much further down the meta-programming route. However, such facilities should definitely be handled with care (it would also be nice to see research into how TMP can compile faster).

    There is another issue regarding C++s syntax overall and how if we modernised the syntax and made it easier to parse we could have faster build times and easier to understand code to boot. That is definitely out of the scope of Scott's new book though!

    Regards,

    Ben

    ReplyDelete
  26. I think the discussion of auto vs. explicit type declarations could also use a mention of lambda return types.

    I've been bitten once where I created a one-line lambda without specifying a return type, not thinking about the fact that the matrix library I was using made heavy use of expression templates. As a result, the expression returned from the lambda (something like a * (b + c)) contained references to temporary sub-expressions (in this case, (b + c)) that were implicitly constructed and then destroyed within the lambda, resulting in dangling references in the returned expression.

    The solution was to explicitly declare the return type of the lambda as a plain matrix object, to force evaluation of the expression before returning. I thought it was an interesting example of how automatic type deduction can get you in trouble even when you're not *explicitly* using it.

    ReplyDelete
  27. @Ian Mackenzie: Nifty example, thanks!

    Scott

    ReplyDelete
  28. "Prefer pass-by-ref-to-const to pass-by-value for std::shared_ptrs"

    I'm not so sure about this one. We've had a discussion with my colleagues at work recently as we have a lot of shared_ptr instances passed by value. One of my colleagues made me thinking that this actually shouldn't matter as in most cases the compiler should elide copies if it can see that the shared_ptr object is not used in the caller's context. If the caller uses it somehow, then you need to make a copy anyway. Either way, with one extra (needed) copy or without, it should "trickle" down to the "consumer" without other copies (elided), even if there are many calls in the middle just passing it down. I admit I'm still not sure about it but this is just a thought that it might not be obvious.
    See also one of the answers to the GotW#105. Looking forward to seeing your views on this matter.

    As to unique_ptr a pragmatic approach could be to use some sort of make_scoped() utility for any sort of resources which returns unique_ptr as suggested in the comments above.

    ReplyDelete
  29. @Kris: There are too many facets to the issue for me to elaborate on everything here, but the key problem I have with pass by value for shared_ptrs is that it's not free, even if copies are replaced by moves. To pass by value means to call a constructor: the by-value parameter must be constructed. For the shared_ptr implementations I'm familiar with, a move construction costs four pointer assignments. (Copy constructions are more expensive, but, in my experience, most people concede that copying a shared_ptr can be pricey.) When the parameter is destroyed, there is a call to its destructor, and that will incur the cost of a reference count manipulation. Whether that cost can be legitimately counted depends on the details at the call site. For rvalues, you typically pay for the refcount decrement at the end of the call, anyway. For lvalues, you'll eventually pay it, but you may not have to pay it until much later.

    Passing a shared_ptr (or anything else) by reference costs just the cost of a pointer assignment, because references are implemented as pointers in the underlying object code. Furthermore, it might even be a particularly cheap assignment, because the pointer might be stored in a register.

    So if your choices are between pass by value and pass by reference for shared_ptr, even with move construction, you're talking four pointer assignments versus one. The calling code looks the same either way, as does the body of the called function. For a 75% reduction in cost, then (4 assignments reduced to 1), all you have to do is type "const std::shared_ptr<T>" instead of "std::shared_ptr<T>". I think that's a reasonable trade-off.

    As I've written elsewhere, I don't want to get dragged into putting the justifications for my tentative guidelines in this blog. But this issue arises often enough that I wanted to get some basic information out there. The full treatment of the issue is more nuanced and involves consideration of more interactions of language features.

    Scott

    ReplyDelete
  30. The issue isn't that shared_ptr const& isn't better than shared_ptr by value, if you don't need to receive ownership. The issue is that it's inferior to a plain T* or T& in this respect.

    There's no place where const shared_ptr& is better than T&, but not worse than shared_ptr by value.

    ReplyDelete
  31. One thing I haven't seen mentioned, that I ran into myself awhile back is objects that own std::thread objects and use a member function as the thread proc. Owning the std::thread means your object becomes move-only, as std::thread is move-only. Moving an object whose thread-proc is a member function is a very bad idea since it invalidates the this pointer the member function is likely to be relying on.

    Of course storing any such objects in an STL container means they may well be moved for perhaps non-obvious reasons (re-allocation, re-balancing, etc..).

    If you are lucky you will get a quick crash after the move, if you are unlucky the thread proc keeps running using a stale this pointer and moved from data fields.

    Perhaps that leads to another tip, have your move operators write sentinel values into the moved from locations, otherwise bugs like this become hard to track down as the member fields after move can still look totally valid.

    ReplyDelete
  32. Hi Scott, Good luck with this book!

    In my experience in using and explaining C++11 one of the most powerful ideas has been "'never' write new or delete". Your old chapter 3 and item 13 ("Use objects to manage resources") does as you explained in your answer to DeadMG already tell what you should do, but with C++11 it became so much easier and powerful.

    I understand your decision to write a new book and that it should not repeat what's already in Effective C++. However,
    adding move semantics and movable smart pointers relates much more to RAII than many might first think. It changes the best practices on how do we create, hold, destroy and pass objects. Advice on each of those steps would be good topics. Several of this kind already came up in your lists (eg universal references and using shared_ptr) and the other comments [preferring pass-by-const reference and to prefer unique_ptr before shared_ptr, and something on unique_ptr deleter (I have no preference on the last one)]

    To be concrete I suggest advising against bare new when initializing smart pointers, in favor of make_shared, and perhaps also make_unique, which in my mind is also part of the accepted best-practice. One could argue that you are anyway fine if you remember item 17 (Store newed objects in smart pointers in standalone statements) but with make_shared and make_unique you don't even need to think about those issues anymore.

    A related subject is class design with move support: such as rule-of-zero, and
    * Declare move constructors noexcept
    ... to allow standard containers to call them. Forgetting that is such an easy thing to do.

    ReplyDelete
  33. I would change the "Distinguish () and {} when creating objects" to "Prefer () when possible and only use {} if you want initializer_list constructors."

    Consider this code:
    struct Widget
    {
    Widget(int);
    operator int() const;
    };

    int main()
    {
    Widget a{5};
    Widget b{a};
    }

    If I now add the following constructor:

    struct S{ S(int) {} };
    Widget(std::initializer_list<S>);

    Then that will break both of those lines because the initializer_list constructor will be preferred to the int constructor and to the copy constructor.

    So you should
    1. Be very careful when you add a initializer_list constructor to a class that is initialized using {} initialization.
    2. Be very careful about adding non-explicit conversion operators if your class is used as an argument to {} anywhere.
    3. Be very careful about adding non-explicit constructors that take a type that is used as an argument to {} anywhere. (this is the same rule as 2. except for conversion constructors instead of conversion operators. it's wording should actually be even more complicated, because you also shouldn't add conversion constructors for types that arguments to {} can be converted to, which is what broke the copy construction in the example above)

    Basically if you use {} initialization you have to know ahead of time whether the class will ever add an initializer_list constructor. If it may do so in the future, prefer () initialization.

    Also I think it's worth pointing out that you should never use {} initialization in template code, because you simply don't know what it will do. For example std::vector<T>{10} does something different depending on T. Insert std::string or the Widget from above for T. For one it will create ten elements, for the other it will create one. Also consider something simple like this:

    template<typename T>
    void foo(const T & arg)
    {
    T local_copy{arg};
    // do something with arg and local_copy
    }

    This will usually do what you want it to do, but consider
    foo(std::vector<std::function<void ()>>{});
    (compiler error. starting at libstdc++ 4.8 there is a nonstandard workaround)
    or
    foo(std::vector<boost::any>{});
    (no compiler error, but does not do what you expect)

    Basically if you use {} initialization in your template code it is likely to break on some type that you didn't expect it to break on. Even if you are just forwarding arguments it may not do what you expect it to do or it may work now but change it's meaning in the future.

    Because of all these things I think that Effective C++11 should warn against using {} initialization. Especially in template code. It has a real chance to become a mess in code maintenance because of how it can silently change behavior. () initialization will only change behavior if you provide a better match for some given arguments, but {} will happily switch to a worse match if it means that it can use an initializer_list constructor.

    ReplyDelete
  34. I have to agree- the unfortunate fact is that uniform initialization is completely broken. There's a big thread in the proposals forums detailing exactly how broken it is, but recommending against it is solid advice.

    ReplyDelete
  35. @DeadMG, Ryan Molden, Johan Lundberg, Malte Skarupke, and anybody else who has or will leave comments: I'm falling behind in responding to comments, and I regret that. I value all the comments that come in, and I not only read them all, I think about them. Some change my opinion on things. Others suggest areas for further research and deliberation.

    It's becoming clear that I'm not going to have time to respond to every comment that appears here, even though I'd very much like to. So let me simply say again that I find all comments useful, and I really do think about what they say. My list of Items for EC++11 is tentative, because I recognize that my understanding of C++11 and its effective application is imperfect. Please keep those comments coming, because they'll help me produce a better book and, by extension, better developers among those who read it.

    Thanks for your understanding.

    Scott

    ReplyDelete
  36. An example of how auto can lead to unexpected behavior:

    std::string mystring ("testzing");
    std::string error = "z";
    auto it = mystring.find(error);
    mystring.erase(it, it + error.size());
    std::cout << mystring << std::endl;

    This will print "test" and not "testing".

    The member function find on a std::string won't return an iterator, but it will return a position. The member function erase is overloaded. There is one that takes 2 iterators, and one that takes a position and the number of characters that need to be erased. In this example it will take the latter one and hence it won't erase 1 character, but the value of it+1 characters.

    ReplyDelete
  37. constexpr is not only about metaprogramming, as it truly adds features that were lacking in C++98. For example, you can access the contents of a constexpr array at compile time (to, for example, sum the elements of the array at compile time, or perform static_asserts on them), something that was impossible in C++98.

    Also, when you really need to calculate something at compile time, doing it with a constexpr function is certainly easier, shorter and more readable than using template metaprogramming.

    I'm also guessing that parsing and interpreting a constexpr function is faster than the equivalent template metaprogram (and it should also take significantly less memory), so rather than slow down the compilation, it could ostensibly speed it up.

    If the worry is that constexpr functions will increase the popularity of compile-time calculations, making compilation in general slower, that remains to be seen. Will the compilation times be significantly slower, or will they only be marginally so? As compilers are improved, I'm expecting the compilation speed of constexpr functions to only increase with new compilers.

    ReplyDelete