6 ms·
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
by thebear 12y ago
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'"?
- nawitus 12y agoI 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.
- thebear 12y agoYes, 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'.)
- scott_s 12y agoIndeed 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 https://news.ycombinator.com/item?id=8137332
- squeaky-clean 12y ago>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.
- Silhouette 12y agoThis 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.
- thebear 12y agoNice 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.
- maxlybbert 12y agoIf 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.
- roel_v 12y ago"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++.
- EvenThisAcronym 12y agoI 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.
- roel_v 12y ago? 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.
- flebron 12y agoIt'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.
- thebear 12y agoOk, 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.
- theseoafs 12y agoYour 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.)
- squeaky-clean 12y ago>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.