Hacker News new | comments | show | ask | jobs | submit login
C++11/14 Idioms I Use Every Day (seshbot.com)
66 points by ingve 1188 days ago | hide | past | web | 59 comments | favorite



Raarrggghrhghh. I couldn't possibly disagree with his proposed use of 'auto' more. Abundant use of auto is the opposite of readable! Nothing is more infurating than jumping into a new bit of the code base and having no idea what anything is or what it can do.

Auto is acceptable for iterators and, in some cases, references to complicated containers. Don't fucking type "auto pos" instead of "Vec2i pos", "Vec3f pos", or one of many other possible types that pos variable may represent.


Ah yes, the type inference argument. I give the onboarding here for new hires about C++ and so I get some form of this objection (and the subsequent argument) every two weeks like clockwork. Here's what I say (feedback welcome):

1. Sometimes `auto` helps readability immensely. Sometimes it hurts immensely.

2. All generalizations about when and where to use it are generalizations and likely to start an argument.

3. Ultimately it's up to you and your diff reviewers to do the right thing. There will be people reading your code in the future and they will judge you and your decision, so choose wisely. Future-you included.

4. `auto` can often improve correctness/generality, not just readability, e.g. "int sz = v.size(); //BAD". No one writes "vector<int>::size_type sz = v.size();" I dare you to search your code for it.

5. The only controversial generalization I feel safe making, knowing saying it will get me into trouble, is that experienced non-C++11 (whether coming from C++03 or other languages) coders tend to be more apprehensive and cautious, using `auto` less than experienced C++11 programmers. Take it for what it's worth.


> No one writes "vector<int>::size_type sz = v.size();"

No, but plenty of people write "size_t sz = v.size();" which is equivalent (or superior) in the vast majority of real code. And in the rare situation where the return value of size() is not an unsigned integral type, obscuring that fact with auto is bad form.

The sensible generalization for auto is to use it to avoid specifying ugly template specializations when they are both clear from the context and otherwise unavoidable. So "auto it = x.find(f);" is clearly superior to "std::unordered_map<foo, bar, foobar_hash>::iterator it = x.find(f);" etc.

The real danger with auto is to use it when you don't know what the real type actually is, which is what people are tempted to do. But if you type "auto sz = v.size();" when you've specified some unusual template arguments that cause size() to return uint16_t, you're now obscuring the unusually small width of sz which could plausibly lead to integer overflow.


1. Agree

2. Agree

3. Agree

4. I don't use vector<int>::size_type and I don't use decltype(sz) throughout the rest of the function. I use size_t and let the compiler give me an error if size() happens to return a different type or, if when making use of sz, types get mixed.

5. That's a little too anecdotal and 'appeal to authority' for my liking.

The big question, I suppose, is defining guidelines for when auto helps readability and when it hurts it. My experience has been that it is best to default to no auto and only use auto after trying the non-auto way first. Iterators and template-heavy containers are great instances where auto helps. In our codebase I can't think of another situation in which auto would be useful.

We also don't have heavy template usage outside of containers. If your code has templates everywhere then I can imagine the number of times auto is an improvement in readability would be larger.

There have been exceptionally few times where I have jumped into someone else's code and said "wow, their heavy use of auto has made this easier to read and understand".


I've no dog in this fight, but narrowly with regard to

'There have been exceptionally few times where I have jumped into someone else's code and said "wow, their heavy use of auto has made this easier to read and understand".'

I think it's far easier to notice when you hit unreadable code and what seems to be making it unreadable, than it is to notice when you hit readable code and what seems to be making it unusually readable. That's not to say your conclusions are necessarily wrong (or right), by any means - just that I'd view this particular argument with an added measure of skepticism.


I've got a legit question that sounds sort of like trolling but I can't think of a better way to word it.

For point 5 where do you find these experienced C++11 programmers that aren't coming from earlier flavours of C++?

It's possible that I misunderstood what you read so in that case could you please clarify what you meant?


What I mean is that (and again, I know this is a gross generalization), on average, experienced engineers new to C++11 tend to be pretty anti-auto and tend to soften their stance on `auto` over time.


His advice on the use of auto seems perfectly sane to me. The first time I learned about a language with type inference for local variables was D, and coming from C++/Java, I had the same reaction. I absolutely hated it. However, the D community seems to overwhelmingly prefer auto/const/immutable/etc. (any of these can be used in place of an explicit type declaration in D), and over the years as I've worked with other languages that have adopted this convention, I find that I now cannot stand any unnecessary explicit type declarations in my code.

Nearly 100% of the time, you know what the type of a local variable is (otherwise people would be unable to use Python/Ruby/Javascript/etc.), so it's no great harm to readability. Furthermore, you are probably already working with an IDE that can tell you the type of a variable when you hover over it, anyway. I think that it also leads you to think more about interfaces than specific types.


I also flinched at the advice to use 'auto' wherever possible. When I'm doing some refactoring I prefer to have the compiler throw errors and warnings when types change. Let's say I have a method which returns a map<int,int> written as auto:

auto bla = obj->GetBla();

bla[x] = y;

The assignment adds a new element or overwrites an existing element to the map, it will never crash.

Now during a refactoring obj->GetBla() is changed to return a vector<int>. In conventional code the build would break with an incompatible type error, the coder investigates, sees what's wrong and fixes the problem. With the auto statement the code would compile, but the bla[x] = y; would very likely not do what was intended (it can overwrite foreign memory or produce a segfault).

Is this not seen as a problem? I do use auto here and there where the type is obvious (for instance written on the right-hand-side of the assignment), but using 'auto wherever possible' is terrible advice IMHO, since it completely undermines type safety.

[edit: typos and formatting]


I started a separate thread on that subject earlier. It didn't get a whole lot of attention, but FWIW: search for "thebear" on this page to find it. Specifically, you may like the employee vs. nuclear missile example that someone posted in that thread. That nicely illustrates our point.


Why don't you use an IDE that shows you the type?

I do agree that jumping into a new code base can be slightly more trickier if type inference is used, but it's a minor point compared to the benefits in my opinion.

If you cherry pick something like Vec2i, type inference doesn't look that good, but the benefits are clearer from more complex code.


because the code base has several million lines and eclipse is a horrific user experience?

The example posted is a horrible use of autocomplete.

>const int calculated = [&] { auto l = lock(); auto first = stage1(); auto second = stage2(); return combine(first, second); }()

No idea what that does. It takes 2 variables of some type and does something too them. There is some sort of lock there too. I guess.


The problem there is not with auto, it's with the function naming. If you don't know what the function lock does, how would writing Lock l = lock() help? Also, stage1 and stage2 are awful function names in this context.


> stage1 and stage2 are awful function names in this context

Clearly that was an example created to demonstrate the lambda idiom, it wasnt real code


Does

  return combine( stage1(), stage2() )
bother you too? because that infers the return-types of stage1() and stage2() as well.


yes, for different reasons.


So is the core problem that there's no decent IDE for C++ which can identify the "autocompleted" types to the developer?


No, the core problem is a developer that thought "auto" exists to make code easier to write instead of existing to make code easier to read.


I use sublime text and it works fine, combined with a robust code search tool.

The problem here is poorly written code. I shouldn't need an IDE to tell me what is going on, the code should do it.


That's alleviating the symptom, rather than fixing the problem. When one needs an IDE to read a plain text file, it's time to stop.

Why don't I use an IDE to read it? No, how about why doesn't the original coder write the code for other people to read, instead of forcing me to use an entire IDE just to piece together what they're doing, like I'm some kind of code archaeologist.

I'm debugging this thing across a flaky modem line through which I'm getting an echoed terminal, I've managed to get gdb running and narrowed it down to a handful of suspicious lines of code, and the gimboid who wrote it assumed that everyone who ever looked at it would actually be using Visual Studio 20-whatever with all the plug-ins, rather than plain text a line at a time. Brilliant.


If I'm reading a new function I shouldn't have to mouse over, or f12, a dozen variables to figure out what they are. I should be able to tell just by looking at text.


hey, I updated the post (though I didn't lower the polemic attitude!)

I just added links to several of Herb Sutters articles (e.g., http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-... ) and Scott Meyers talk (http://vimeo.com/channels/ndc2014/97318797 )

They say it better than I really could, and they say it very emphatically. I think the general consensus amongst the C++ leadership is to use auto when possible, for more reasons than I had considered.

edit: I notice you also object to 'appeals to authority'. In this case I am not saying do it because all of the known C++ authority tell you to do it, but indicating that they /also/ recommend it, hopefully adding more weight to the argument

Also, what's wrong with appealing to authority on difficult subject matter? Whats the point of attending Scott Meyers' lectures at all if you aren't going to heed his advice???


Has anyone taken a look at the new versions of Bjarne Stroustrup's C++ Programming Language [1] or Scott Meyer's Effective Modern C++ [2]? The originals are classics, and I would be interested to know if the new versions hold up.

[1] http://www.stroustrup.com/4th.html [2] http://shop.oreilly.com/product/0636920033707.do


I read his short A Tour of C++ and enjoyed it a lot. Much of it is taken from parts of his update to The C++ Programming Language. If you just want a short book to introduce the new parts of C++11, A Tour of C++ is the way to go.


I got [1]. (My first copy had several sections misaligned, which I thought would make a hilarious "offset" bug, but no one else did, so I sent it back for a replacement.)

It's interesting and readable. I used to keep a listing of all the typos and errors I found but that project got buried beneath other things in my stack.


I have the 4th edition of C++PL, and while I haven't read it cover to cover (it is long) the parts I've read have been excellent and definitely meet or exceed the standards of the earlier editions.


The 4th edition of Stroustrup is quite good, and much better than the 3rd edition, which was not even very usable as a reference, due to a terrible index. It's still not a page-turner, though.


Thanks for this blog post; it will help me to make the best use of C++11/14 features in day-to-day programming. One thing I've been wondering about for a while is how the auto keyword relates to type-safety. The blog post lists the point "auto type deduction everywhere" under "type-safety". But consider the line

  auto x = foo();
Suppose you refactor your code, and in the process, the return type of foo() changes. Without the use of auto, the compiler would produce an error message, alerting you that the above line of code may need your attention. As it is, with the use of auto, the above line will compile no matter what. In other words, that particular line now behaves as if C++ was an untyped language. Isn't that a step down as far as type-safety is concerned? Please note: I'm not asking here whether that's good or bad. I'm asking, "Assuming that we use the auto keyword throughout, can we still call C++ 'strongly typed'"?


It's going to be untyped in the same sense that the following Haskell code is untyped:

    foo = [1, 2, 3]
    x = foo
We didn't give a type to x, but the compiler will infer it anyway. If foo changes, x will change too. This does not make Haskell untyped, it just means it can add type annotations by itself in most cases (see Hindley-Milner type inference).

Note that that code won't "compile no matter what", because in C++ construction using "=" is already an operation that not all types have. So if foo returns an object which does not have a copy constructor or move constructor, then that line will fail to compile. In the more general case, you'll still not be able to use operations on x that x's type does not support, and that's what type safety means.


This is all right and well on a theoretical/academical level, but I completely agree with the grand-parent. If somebody on the team changes the return type of a function to something which 'looks' like the old type (so the compiler would happily compile it for me if 'auto' is used), but the produced code is suddenly much more expensive or even does the wrong thing, then I'd like to know this at compile time, and don't wait until it blows up at execution time.

Of course it is also possible to introduce stuff like this during refactoring without changing the type, but the more information the compiler has to find possibly regressions, the better.


Ok, I got you. If type-safety means, as you say, "you can only use operations on x that x's type supports", then all is well. I must admit that I was under the impression that type-safety meant more: if I assign to a variable x of type A an object of type B, then that is an error even if the type B will work syntactically (i.e. has all the operations needed by my variable x), because it may not work semantically.


>if I assign to a variable x of type A an object of type B

You're correct that this is unsafe, but this isn't what auto does. You'd be assigning a variable x of type A an object of type A. Then what that code theoretically changes, you're now assigning variable x of type B an object of type B. auto just handles all the boilerplate. It will be type safe, it's just logically incorrect.

For example

    public int foo()
    {
        return 1;
    }

    auto x = foo();
    //is the same as
    int x = foo();
And then you change it to this:

    public float foo()
    {
        return 1.0f;
    }

    auto x = foo();
    //is the same as
    float x = foo();
In either case you can go on to do stuff like 'float y = x / 10;' Because they're both valid operators. But there can be logical inconsistencies, like the integer division problem that will occur if x is an int. Then again, you probably shouldn't use auto for something as simple as int or float. But 'auto foobar = new vector<int>;' is a very clear statement, and if you replace vector with something else, odds are the compiler will throw a fit with any later methods you attempt to call.

I haven't written C++ in a while, so I hope I didn't butcher the examples too much. I simply typed them into the comment box.

The definition of type-safe could be extended to mean what you say, it's not really a term that is set in stone. But I've always used type safety to mean preventing type-based syntax errors, not semantic errors. If it's more common to do otherwise, I'd be happy to be corrected.


You shouldn't use auto for float and int not just because they're simple, but because they get happily coerced in ways that might break things. I've not written much C++11, but my intuition would suggest limiting auto to places where, upon a change, either 1) everything in the dependency chain will change and work correctly, or 2) anything that can't change correctly will loudly break at compile time.


Your mistake is assuming that the "type of a variable" has to be explicitly denoted by the programmer in some way. That's not the case in a lot of modern statically typed languages, which have type inference (OCaml, Haskell, Rust, modern C++, etc.)


I think the compiler would give an error if the variable x is (later in the code) used in a way that's forbidden by the type.


This is true, though there are still real risks in just using auto everywhere.

For one thing, duck typing is always vulnerable to code that is syntactically correct but semantically nonsense. For example,

    employee.fire()
and

    nuclear_missile.fire()
have quite different meanings, but if all you write is

    auto x = get_an_x()
    x.fire()
then nothing in the language or compiler will stop you or notice the changed implication in this scenario.

C++ is also vulnerable to a slightly more sinister variation because many conversions can be made implicitly, and sometimes those conversions are lossy. For example, suppose x was converted from an integral value to a floating point one because get_an_x needed was upgraded to offer more precision. Unfortunately, all the code you've got that later compares x to a known value using == might now be broken (comparing floating point values without a suitable tolerance) and again depending on how you've got your compiler's warnings set up you might never notice.

If C++ had a stronger type system, then at least the latter issue would be less dangerous, but with the language we're talking about today I think over-use of auto is a risk that shouldn't be ignored in code reviews.


Nice example with the nuclear missile. For those to whom this seems contrived, let me explain why the issue of type-safety and semantics is so important to me. I used to work in symbolic computation. In that world, it is very common for a large number of types to have the exact same operations, although they are semantically very different. Example: groups. If type-safety is a purely syntactical notion, then type is no longer a tool to enforce the distinction between different kinds of groups, such as abelian groups, torsion-free groups, etc.


Yes, that's correct. It is, in fact, the same kind of behavior you get with templated code: you can instantiate your template with any type you wish; compile errors will occur when the instantiation is used in ways that are forbidden by the type. This behavior has been recognized as less than ideal, and the issue will be addressed by concepts. My question is: if we have this behavior not only for templates, but for every variable, isn't that less type-safety? Isn't that less than 'strongly typed'? (Come to think of it, I think I'm really asking about the definition of 'strongly typed'.)


>My question is: if we have this behavior not only for templates, but for every variable, isn't that less type-safety?

It depends on how you code, I suppose. I use var very frequently in C# now after being averse to it at first. I've never run into an issue where var caused problems in my code (rarely my IDE won't be able to pick up on what type it should be, but it compiles fine).

If you're changing a variable between compatible types, like a parent class, or changing to a different type of enumerable class where usage is exactly the same, you don't really run into issues. If you're changing the entire usage of a class (like from a vector to an array, or something even more different) than you probably should not simply re-declare the variable, you should be rewriting that code.

You wouldn't change

    vector<int> numbers = new vector<int>;
to

    int* numbers = new int[512];
Without making accommodating changes to the following code, would you? Then you shouldn't change

    auto numbers = new vector<int>;
to

    auto numbers = new int[512];
Without making changes to the rest of the code either. It's exactly the same as before, except you get to type less and it clutters the screen less when you don't need to write types multiple times on the same line. Especially for very long types names.


Indeed you are. There was a post from two weeks ago on the topic, called "What is type safety?" HN discussion here: https://news.ycombinator.com/item?id=8137332


If the code compiles without error on the new type, then there is a good chance that there is no need to worry about it (unless, say, the developer changed "int foo()" to "short foo()").

And if you use "x" in some way that isn't allowed by the new type, you will still get a compiler error; although the error will be on the line where you use x and not on the line where you declare x. The compilers I've used (Visual C++, GCC and Clang) will tell you where x was declared in the error message.


"If the code compiles without error on the new type, then there is a good chance that there is no need to worry about it"

Yeah well I'd rather not rely on 'chance' - if I would, I wouldn't be using C++.


I think you've been using a different C++ than I have, then. Numerous implicit (read: invisible and unexpected) conversion in C++ account for a large swathe of bugs. Sure, it's deterministic as to which function is called, but throwing a die is deterministic as well, as long as you know about every variable that affects the roll.


? I can't even remember the last time I've seen a bug that came from implicit casting, apart from there being compiler warnings that will tell you about them.

I don't see how one can reasonably argue that type conversion is like 'rolling a die' - well yeah, if you use auto all over, it can be, which is my whole point. Next thing we'll need unit tests just to make sure a type change in place A doesn't make part B fall over - just like in scripting languages. Judicious use of auto to make iterators less painful to type; OK. 'auto' for types in templates where you don't know the type yet - that's a genuinely useful case (although there aren't that many cases where it's useful). 'auto' for every type just because that way you don't have to think about types - recipe for disaster and source of unexpected bugs, imo.


I'm loving the new pseudo-random number support. In a few lines one can crank out some Mersenne twister niceness. No more shipping around custom pseudo-random code, and no more calls to rand that give different results for the same seed on different platforms!


The nice thing about C++11 is that the designers seem to have actually tried to increase usability, rather than just maximizing expressivity, which is usually the case. Features like range-based for and auto make programming much more pleasant. I program in a functional style in many other languages, but could never bear to in C++ before because it was so awkward.


I still wouldn't try to use too many functional idioms in C++ even now. It's just not a good language for that kind of programming style. For example, from the "New language features" section of the C++14 Wikipedia page[1], the first example is generic lambdas, which means we can now write:

    auto lambda = [](auto x, auto y) {return x + y;};
That's progress of a sort, but in Haskell, a language designed to support this style of programming, it looks like this:

    lambda = (+)
Obviously C++ is slowly being left behind by modern programming language design and the collective experience of the programming industry over several decades since C++ was invented. Eventually it will be superseded by other languages that do support more expressive language features and better designed standard libraries (which is easy to do with the wisdom of hindsight, of course -- this is not a criticism of the people who built C++ without the benefit of that hindsight).

But since we don't yet have any such alternatives that don't also have significant drawbacks and/or unacceptable run-time performance characteristics, for the near future C++ is still going to be the first choice for a lot of projects in the real world. In that respect, closing the loopholes and fixing the pain points is useful for a lot of people, and very wisely that seems to be what the standards committee are concentrating on doing.

[1] http://en.wikipedia.org/wiki/C%2B%2B14#New_language_features


I agree with your example. But, for example, before C++11, if I wanted to do an even slightly non-trivial map or filter I'd probably do it by hand rather than write a (C++) "functor" or assemble some function in place with std::bind1st et al., whereas now I will happily write a simple lambda.


I agree, but in C++, I probably wouldn't try to wrap that kind of logic up in a functional programming style with the standard algorithms anyway.

I've read the advocacy and I've considered the arguments many times, and in a language designed to support those styles I would absolutely agree that using them is a huge improvement over a raw loop. But in those languages, the functional style has clear benefits for both readability and maintainability.

Unfortunately, C++ is not such a language and the benefits of trying to shoehorn that programming style into C++ projects are far from clear, even with the new lambdas. Therefore, for logic that isn't completely trivial, I usually still choose to write the loop.

I suspect we actually have quite similar preferences about programming style, and I don't like that favouring raw loops is the conclusion I've reached. However, as with some of the libraries using terribly clever template wizardry, I find sometimes in C++ the medicine is worse than the disease.


Looks like a great list of handy things from the latest versions of C++. I think it might have been a non-standard addition before C++11, but I really like the data() function on vector. It just gives you a pointer to the first element of the array of data in the vector. Obviously you can shoot yourself in the foot pretty badly with this, but it's great for interfacing with legacy C code, etc. that expects to get a pointer of things and length.


I think you could also have done something like "&(v[0])" to get the same thing.


Watch out! "&(v[0])" has undefined behavior if v is empty. "v.data()" always works, besides being more readable.


I looked this up:

"Returns pointer to the underlying array serving as element storage. The pointer is such that range [data(); data() + size()) is always a valid range, even if the container is empty."

Can someone explain the semantics? If the range is valid for an empty vector, what does that say about the validity of tge pointer returned? What value will it have for an empty vector?


On an empty vector, data() can either return a null pointer, or a pointer to some allocated memory with suitable alignment for that type, but there doesn't have to be a existing object.

What is the range [ data(), data() + size() ) anyway? It consists of n + 1 iterators. All but the last one can be dereferenced. You can take one iterator in the range and add or subtract to get another one, as long as you stay in the range. You can subtract iterators to get a signed integer. You can compare iterators with all six operators ==, !=, <, >, <= and >=.

For any valid pointer p, null or not, [p, p) is a valid range of length 0. It has one iterator, p, but you can't dereference it. You can add or subtract 0. You can subtract p from itself, getting 0. And you can compare it with itself.


The pointer will have a non-trap representation, if that is even a thing in C++. I wouldn't even assume it's non-null or not a value shared between different vectors. You can also add or subtract 0 to the pointer, or subtract the pointer from itself.


This was not guaranteed to work in C++98, but has been fixed in C++03 by stating that std::vector has to use a contiguous block of memory.

&v[0] (without the parens) is even shorter :)


Thanks for this. I've been wanting to pick up a bit more new C++, but haven't found a lot of great resources.


Agreed.

Following https://gobyexample.com and http://rustbyexample.com, a http://cppbyexample.com kind of site would be quite useful for people who are familiar with coding and just want to experience some practical use cases in idiomatic C++ (threading, json parsing, etc).


C++11 has some tricky changes which are way not obvious at the first glance. A few useful resources I found:

* http://thbecker.net/articles/rvalue_references/section_01.ht...

* https://en.wikipedia.org/wiki/Copy_elision

* http://en.cppreference.com/w/cpp/language/eval_order

* https://akrzemi1.wordpress.com

* https://dl.dropboxusercontent.com/u/13100941/C%2B%2B11.pdf

Understanding the combination of copy elision and move semantics (when and how they are applied) can require one to read the standard.

C++11 is not simpler than older C++, but using it is easier, if you have a good understanding of it.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: