c++11 iteration and warnings

Started by
25 comments, last by Ravyne 11 years, 1 month ago

The presence of 'auto' is becoming wide spread and is a really really bad practice.

This is a most interesting topic, and I think we are going to see all kinds of practices of this (when to use and when not to use).

There is the recommendation not to repeat yourself, but then there is the problem for the reader of the source code to understand what is going on.

I used to favor the second view (and explictely use the type). But then I programmed Go a lot, and there is a similar mechanism. Assignments can be done as usual with "int a = f()" just as in C/C++, but there is also the syntax "a := f()" where you don't have to declare the type of a. This is used consequently everywhere, and it is recognized as a strength of Go (while still having static type testing by the compiler).

So my personal opinion is that I like to use "auto" for all but the primitive types. And I want the IDE to help me see what the type is.

Edit: spelling and wording.

I tend to agree that this is going to be interesting and I'm sure we'll see a new Herb book about 10-20 ways to write better C++ in the next year related to all the fun things Cxx11 does to the language.

In the case of auto though, I agree overuse could be a problem but in general I don't agree with typedef everything. In code I was working on tonight I had something like the following:


std::vector< Item* > removals;
... do a loop adding to "removals" those items which need to be removed after iteration processing ...

In no other case do I need a vector of that type, it is purely local and very descriptive between what it is defined as and the name of the local it is stored in. Hmm, what could that mean other than, these are things to be removed.. So the end of the function is:


for( auto it : removals )
  something.erase( it ); // It's a set, so no find required.

I find this to be both extremely readable and easily understood by others. Yes, it is a simple case, but the point is can you think of many ways to interpret a well named (ok, descriptively named, "well" named is questionable) variable with a locally given type that could be used in in a different context? If you can use it in a different context, does it still apply to the overall descriptive naming?

In the same code, I have a type "mPairs" which is a "PairMap_t", hopefully you already know what the typedef is.. :) So, using auto in this case:


auto it = mPairs.find( Pair( lhs, rhs ) );

Is still pretty readable? "Pair" in this case is a local class used to contain a bit more than the "lhs/rhs" items but the intention is still clear, find "that specific one" and give me an iterator to it. I "could" add the following: "typedef PairMap_t const_iterator PairConstIterator_t" or some such but what good does that do for readability? It doesn't do anything useful except waste typing really, if you bothered to look at the definition of "mPairs", you know exactly what 'it' is defined as. Seems pretty clean and descriptive to me.

Advertisement

What I find weird about the new range for:

- There is the new cbegin and cend you should use on read access as to avoid the container preparing for a write, like for example a string making a private copy of the shared internal buffer.

- The range for seems to always use begin and end, but never cbegin or cend?

What I find weird about the new range for:
- There is the new cbegin and cend you should use on read access as to avoid the container preparing for a write, like for example a string making a private copy of the shared internal buffer.
- The range for seems to always use begin and end, but never cbegin or cend?


cbegin and cend are used to ensure that const iterators are obtained even if the container is not const. It isn't because non-const begin might do extra work; in fact string cannot use copy-on-write anymore in C++11. With range for, the iterator is hidden but you can declare the element const, which is sufficient.

I'm thinking of stuff like 'std::pair<int, int> p = std::make_pair..." -- not literal repetition, but just as useless.

But std::pair<int, int> p is almost as bad as auto. It conveys structure, sure, but it conveys nothing in the way of semantics. An explicit typedef std::pair<int, int> Point is infinitely preferable to either (though a struct Point {int x, int y;} would be better still).

And while I'm sure that was merely a contrived example, I think the same holds for many common uses of auto.

I agree that a class, struct, or typedef is going to be available in many cases, but the semantics of my example would change along with it. Imagine instead:

Point p = Player.GetPos();

Is it terrible to have to write this? No. Is using auto instead going to lose tons of semantic value? No again, I argue. Each approach has its pros and cons, they just have to be weighed out.

Other times you find yourself needing "throw-away" or "temporal" types -- types that exist maybe in one place, and so don't need to be shared around. You can typedef those too, but I don't think that usually buys any more semantic understanding either. Most times that you use an iterator is a good example of this, lambdas are another. In functional languages, the pattern is to create these kinds of temporal types all the time wherever they're needed, and you hardly even think about it that way.

I do agree though, that over the next probably 2-5 years we'll see some real guidance about when its best to use auto or not. Right now we're at a level of "this is what they're designed for, this is what they can do.", which doesn't always turn out to be the same thing as "this is what you should do."

throw table_exception("(? ???)? ? ???");

Other times you find yourself needing "throw-away" or "temporal" types -- types that exist maybe in one place, and so don't need to be shared around. You can typedef those too, but I don't think that usually buys any more semantic understanding either. Most times that you use an iterator is a good example of this, lambdas are another. In functional languages, the pattern is to create these kinds of temporal types all the time wherever they're needed, and you hardly even think about it that way.

You don't create "temporal" types in functional languages. What are you talking about?

It's unfortunate that no single way of returning multiple values in C++ is really flexible, nice to write and safe/self-documenting.
On one hand, we have
typedef struct { int index; bool error; } foobar_return_t;
foobar_return_t foobar(){...}
auto result = foobar();
// do something with result.index, result.error - readable, little chance for mistakes
which would, IMO, be made more convenient if we were allowed to just write an anonymous struct in the function definition:
struct {int index; bool error;} foobar() {...} // but it's not allowed :-(
On the other hand, we have std::tuple which can conveniently assign straight into existing variables, which is kind of readable:
tuple<int,bool> foobar(){...}
tie(localInt, localError) = foobar();
but if the types are compatible or identical, we could easily assign or access them the wrong way. sad.png
I still like the latter approach for local lambdas where the function definition is visible from where it's being used.

Other times you find yourself needing "throw-away" or "temporal" types -- types that exist maybe in one place, and so don't need to be shared around. You can typedef those too, but I don't think that usually buys any more semantic understanding either. Most times that you use an iterator is a good example of this, lambdas are another. In functional languages, the pattern is to create these kinds of temporal types all the time wherever they're needed, and you hardly even think about it that way.

You don't create "temporal" types in functional languages. What are you talking about?

Its semantics I suppose, maybe "types" is a bit grandious a term if you come from a functional background (like I said, you hardly even think about it that way if you do), but if you were to perform similar patterns in, say, C++ before the auto keyword was introduced, then you'd have to name a new type to do it. Take pattern matching, for instance: you have a bunch of information, you care about some and ignore others, essentially each pattern is like (not is) a distinct type, which feeds through to the expression on the right. Or, you've got two homogenous lists and want to call a function that takes the information as a single, interspersed list, so you zip them together.

Its not so much that you're actually creating types, but you're constantly re-packaging information to look at it in different ways or to pass it to different functions. The point is, though, that in functional languages you're working with an algebra of types, and as long as its irrelevent you never really have to say what those types are, which is a very different notion than is typical of C++, even types defined by template parameters. Functiona languages don't fall apart just because of this "willy nilly" non-statement of types, and the reason that it *doesn't* fall apart is largely because the emphasis in those languages is on thinking about the properties of types, not just what information they hold, as is typical in C++.

throw table_exception("(? ???)? ? ???");

This topic is closed to new replies.

Advertisement