Hacker News new | comments | show | ask | jobs | submit login
Don’t use C++ auto? Restricting auto is not the best decision (swdevmastery.com)
45 points by etrevino 10 months ago | hide | past | web | favorite | 111 comments

This shouldn't even be up to discussion. Auto is better overall

Compilers know types better than humans.

Whoever bans it (whithout a very, very compelling reason like "some of our code has to compile in an old compiler") is being a luddite at best.

This should have been in C++ from its inception to be honest, to avoid blah_type<with_this> something = new blah_type<with_this>() repetition.

> This should have been in C++ from its inception to be honest

It was, but the backslash was even greater back then, so Bjarne removed it from C with Classes.

There are a few presentations/interviews where he mentions it.

That was an entirely different auto. In C89, auto just "variable on the stack". It still had a type. The new auto is just borrowing the keyword since it (a) makes sense and (b) is already a reserved word.

How the keyword was named when Bjarne introduced it doesn't matter, the purpose was the same as C++11 auto.

It was entirely another concept.

Before C++11 auto meant it was not static, extern or register, and in fact every single variable was already implicitly auto if no specifier from the previous list was applied.

    int n = 10;
In that line, n was auto. Now it is not auto, only int.

Type inference is orthogonal to whatever keyword is used.

Bjarne used another keyword in C with Classes, removed it due to backslash, and the commite decided to re-purpose auto for the same purpose in C++11.

No big deal, the meaning of static is also context dependent, and no one is fighting against it like it happens with the new meaning for auto.

> This should have been in C++ from its inception to be honest, to avoid blah_type<with_this> something = new blah_type<with_this>() repetition.

Type inference for this is about the only way to make "enterprisey" C#/Java code palatable.

The only problem is if you're a .NET shop that is a little crusty, and people start having PTSD flashbacks and conflate the C# type inferred "var" with the old VisualBasic monstrosity Variant type.

Personally, I wee it more like the bad old days before "Option explicit" and "Option strict" being turned on and every variable being declared as "dim x" with no type. Sure, that then used a variant in the background, but the chaos it installed is what scares the crap out of some shops with regards to "var" and C#. If you were a good VB.Net citizen, you always newed up your variables like : "dim x as new Y()".

Agree, Golang has := by default

I don't know Go. What does := do?

x := some_code_here says "x is a NEW variable whose type is whatever the result of the expression on the right hand side is".

Say we have a function, GetInt64(), which...returns an int64. x := GetInt64() both declares x to be an int64 and places the resulting value of the right hand side into x. If x was already to declared to be an int64, E.G.:

var x int64

x := GetInt64()

Then the compiler complain that x is not new - it has already been declared. In this case you have to omit the colon to remove the fancy "auto" semantics.

var x int64

x = GetInt64()

That looks a lot like the old Pascal assignment vs equivalence operators. In Pascal (read Delphi) we had '=' for equality and ':=' for assignment. So:

x = 5 <-- a boolean statement, true if x equ 5

x := 5 <-- an assignment of the value 5 to the var x.

TBH, it's only like '=' vs '==' in C like languages. But it's easier to spot errors, as Pascal can never have a valid statement where the two are used in the wrong way.

Interesting. Seems I should take a look at Go.

You also usually declare stuff using this.

For example

    a := "hello"

    a := 0

    a := []byte{0, 1}

Implies the new variable's type to be the return type of the assigned expression.

My rule of thumb is "the type has to be stated clearly and unambiguously at least once on the line the variable is declared.


  auto foo = new Foo(bar);

  auto foo = FooFactory::newFromBar(bar);

  auto foo = bar(baz);

Also, in my opinion, cases where the type is not relevant to the way the variable is used. For example:

    auto itemId = item->id(); // Could be an int, could be a string...

There could be an efficiency bug here.

1. "item->id()" is designed to return a pointer.

2. Consumer of the API writes their code to take a copy of the pointer and passes it along to "processSomeItem". It is cheap to make a copy of a pointer

3. The API is changed and it now returns a reference.

4. The assignment to "itemId" now makes an expensive copy of the object behind the reference causing inefficiency bug.

5. "processSomeItem()" API can handle the copy and the code compiles and runs without the problem being noticed.

Actually performance problems are way more probable when not using auto. Example:

   std::function<void()> = [] { ... };

   auto = [] { ... }
In the first case you are doing type erasure, which adds quite few penalties. Even in other cases, the type you typed might be convertible from the actual thing that is returned, causing extra conversions. If you always use "auto" the chances you use the right type and do less conversions is way higher.

sometimes,declaring lambdas with auto is not not sufficient as i discovered in my "tasks"[1] project. An example place where auto breaks is here[2].

[1] https://github.com/mhogomchungu/tasks

[2] https://github.com/mhogomchungu/tasks/blob/a1512a1b5e0392a06...

The problem there is in your Task::run() function. It now looks like this in the commit I just saw:

        template< typename T,typename ... Args >
	future<T>& run( std::function< T( Args ... ) > function,Args ... args )
		return Task::run<T>( std::bind( std::move( function ),std::move( args ) ... ) ) ;
It should look like this for that example to work without the type erasure (haven't tried, there might be typos):

        template< typename Fn,typename ... Args >
	future<std::result_of_t(Fn(Args...))>& run( Fn&&, Args&& ... args )
		return Task::run<std::result_of_t(Fn(Args...))>( std::bind( std::forward<Fn>( function ),std::forward<Args>( args ) ... ) ) ;
This version is also more efficient, since otherwise you are type erasing twice.

Your code failed to compile but this one[1] succeeds. Side effect of your way is that it now makes the project requiring C++14 but i can live with that.

[1] https://github.com/mhogomchungu/tasks/blob/4210a8fad57958fad...

You can change the `std::result_of_t<...>` for `typename std::result_of<...>::type` to make it work in C++11 :)

All my projects are C++14 so it wasnt an issue for me but i liked being c++11 for other people and i am glad there is a way to get it back to c++11. Already committed your suggestion.

Thank you very much.

I'd expect in that situation that step 3 produces a compiler error at the call to processSomeItem(). (Yeah, they could have overloaded processSomeItem...)

Also, passing stuff by reference does not make a copy.

I'm not sure that it's safe to use an unstable API without being very careful and understanding the breaking changes before updating it.

However, there are many situations where the actual name of the type is only known to the compiler, or is so complex that nobody in their right mind would type it.

The only case I know of are lambda expressions. And for those I would argue that the type is obvious, but impossible to name.

In template meta-programming, there are many cases where you don't know types until your template is instantiated; which, when you're writing libraries, could be with many types. Auto is a 'nice to have' when doing regular OO or generic programming in C++, but it changes (some) template meta programming from 'too difficult to bother with' to 'feasible'.

I can think of cases where the names get so complicated you really don't want to bother writing them out in full, but I'm having trouble coming up with a scenario where you'd end up with anonymous type through templates.

The type, albeit nameable, may not be a part of the public API—it could be an implementation detail subject to change. This is the case with some standard library types such as those returned by std::bind where the exact name of the type is explicitly left unspecified. It is possible to enforce this by returning a type whose name has private visibility—such a type is perfectly usable from the outside of its declaring class except that you cannot refer to it by its name.

Functions can return local types whose name is then not accessible to other code. Other code can use that type via auto though.

As mentioned in another comment, not being able to name or even easily refer to a type without auto is very common in generic code.

This is the general rule of thumb for `var` in C# that I've seen, which has similar issues to `auto` in C++.

That said, there are awful compiler inferred types that C# doesn't have, so `auto` has a much wider application than `var` does.

That rules out one of the core use cases that auto was designed for: iterators.

    std::unordered_map<SomeTemplatedType<A, B>, SomeOtherTemplatedType<C, D>> map = foo();
    std::unordered_map<SomeTemplatedType<A, B>, SomeOtherTemplatedType<C, D>>::iterator it = map.begin();
is much less readable than

    std::unordered_map<SomeTemplatedType<A, B>, SomeOtherTemplatedType<C, D>> map = foo();
    auto it = map.begin();

Any reasoning for this rule? I don't code C++, but in the languages I do use with inference, I just use the maximum allowed inference within a function, and rely on simplicity and good naming to get by.

The reasoning is to know what you're creating. Yes, iterators can probably also benefit from auto. But the concrete bug that prompted some introspection into what the rule should be was:

  void mutate(Foo &foo);
  for(auto foo : manyFoos) mutate(foo);
If manyFoos is Foo[], then the auto type is Foo, rather than Foo& and you're mutating a copy. The goal is to not get screwed by the type inference, so unless you can defend why it's obvious that the type is what it is, don't use auto. An auto that you need to ask the IDE for it's concrete type is a strong code smell.

I go one step further - the interesting part of the type has to be mentioned in a form that you can click on and do a Go To Definition (etc.). So auto foo=FooFactory::newFromBar(bar) is no good, because you can't click on the exact type newFromBar is returning. But you might say auto thing=GenericFactory::Create<Thing>(), and that's OK, because you can click on the identifier Thing.

(The point about the 'interesting' part is to permit stuff like std::unqiue_ptr<T> F<T>() and the like.)

This means that using auto for iterators (which is one of the most useful uses in my experience) wouldn't be allowed.

In the case of `auto foo = bar(baz);`, while specifying the type of the returned value may be useful in many cases, it would also require modification should the return type of `bar` change in the future. So the type specificity can come with a caveat.

I basically only use it for non-scalar variable assignments where the type is obvious, or iterating over collections. I never use it for function return types or parameters; that's just tedious to grok.

Note that since smart pointers showed up about the same time as the revamped “auto”, you should use std::unique_ptr<>, etc. with “new” and not need “auto” there either.

And make use of std::make_unique<>.

auto foo = std::make_unique<Foo>(25.3, true);

The worst part is, you have to be extremely careful here and make sure that you did not mean to write

  auto& foo = bar(baz);

Any decent IDE should be able to tell you the type by hovering your mouse over the variable, even if it's made with auto.

Yeah, but that's corner case. We usually read code on screens, paper, email, git history, etc.

For simple cases yes, but in the cases where you need auto the most (preprocessor or template meta programming) it very often doesn't work, and can't even work because it doesn't know the template types it's being instantiated with.

But why?

Duck typing works fine.

And I thought auto was the best thing ever to happen to C++? Especially when working with STL.

My problem with auto is that Visual Studio loses all ability to predict code when you use it. So when I do Type object and then do object. I get all the methods/members available for that Type. But if I use auto, VS is either can't recommend anything or recommends the wrong set of methods entirely. It really slows me down as a programmer and I hate using it for that reason.

It worked fine in VS2015 and works fine in VS2017. It doesn't work in cases where it cannot work, as in

``` template<typename T> void f(const T& t) { auto val = t.some_method(); } ```

and here it cannot work because T can be an arbitrary type and there's simply not enough info for intellisense to work and suggest members on val.

Qt Creator handles this problem differently. If you call f<int>(0) and f<uchar>(0) within your code, then the Clang code model will process both in the background, check for any syntax errors, and show an error if either one fails.

It's very useful while coding because you don't have to wait until compilation time to see an error, and it's a feature I wish VS would implement.

Meta: HN doesn't support markdown, use two spaces to get code formatting. Full docs here: https://news.ycombinator.com/formatdoc

> My problem with auto is that Visual Studio loses all ability to predict code when you use it.

That sounds more like your problem with Visual Studio.

That seems to be a VS problem. They could easily infer the type and give you correct auto complete. It works perfectly fine in C# when you see var.

VS handles it just fine, this is something specific to the parent.

Definitely shouldn't be restricting how you code based on your IDE. If VS can't handle auto, either update for a fix or switch to CLion, it works just fine there.

If the environments I code for only use VS(Xbox One/PS4) then I think it's fair to code for that IDE. We use Visual Assist at work and auto + hugely templated classes are a complete fail, it just gets no predictive text at all.

Then go complain to Microsoft that their IDE isn't up to snuff.

Try Resharper C++ from JetBrains.

Unfortunately, Resharper doesn't handle large code bases. C++ or C#.

handles ours - which is essentially close to the Dark/Gtk Radiant, but I agree - that's probably more like medium to large project, than large.


it's unfortunate how bad some tooling is in the c++ world.

Visual Studio is fine with auto.

Are you using a plugin like Visual Assist? It's a great plugin, but it does have issues where it behaves as you describe.

Yeah, we use Visual Assist here, I suspect it's the reason behind this issue but then again it's a lot better than Intellisense in day-to-day use so.....

So this is a bit of a tangent, but Julia has this ability to use their typing system in such a way that lets you restrict what subtypes are allowed to be assigned to a variable (I'm just using Julia as an example because that is where I first saw this, I'm sure it originated from Scala or something; I'm not a CompSci historian). So if we have these abstract types:

    abstract type Number end
    abstract type Real     <: Number end
    abstract type AbstractFloat <: Real end
    abstract type Integer  <: Real end
    abstract type Signed   <: Integer end
    abstract type Unsigned <: Integer end
... then I can make a function that takes any value as long as it's a subtype of Number, which implies I can do addition and subtraction and all the other things all numbers can do. So I still have the type safety of not ending up with an object where I want a number, but it is still extremely easy to write generics in Julia[0].

Can you do something like that in C++? So like:

    auto <: SomeBaseClass
Meaning auto would work just like it normally does, except that if you assign something that is not derived from SomeBaseClass the compiler complains, giving you some extra safety checks when composing code with auto all over the place.

[0] https://docs.julialang.org/en/stable/manual/types/

If you're curious, that feature is called bounded polymorphism [1]. It's supported by Java and Rust, too, and I'm sure other languages as well. As mattnewport said, something similar is in the works for C++.

[1] http://wiki.c2.com/?BoundedPolymorphism

Thanks, now I know what to search for next time :)

Concepts give you something like this for template parameters, so instead of saying template<typename T> you can say template<Numeric T> where this would constrain the template to only be instantiable with type satisfying the Numeric concept. There's also some syntactic sugar where you can just say auto f(Numeric x) and this means template<Numeric T> auto f(T x).

Concepts are in the process of standardization and are expected to be part of C++20.

This is nothing to do with inheritance though in C++. Concepts are type constraints and don't require any particular inheritance hierarchy which is a much better way to handle things than using inheritance for this purpose.

Thanks for the explanation! I vaguely remember reading about concepts and didn't quite get how they would be used in practice, but this seems like a good example.

Hm. I thought using auto was the recommended, modern way of writing C++.

It is.

Some people don't want to learn how it works, and have been bitten by unexpected results. Like nearly everything in c++, there are some odd corner cases.

However, auto is a big net win. You should understand it, and you should use it.

> However, auto is a big net win. You should understand it, and you should use it.

Which is a fair summary of the linked article.

The title is misleading. It starts with that argument as a preamble, but the bulk of the content is a detailed technical explanation for how “auto” works in C++.

I was expecting some style guide rules of thumb.

Scott Meyers does a good job of explaining “auto” in Effective Modern C++.

Rules of thumb work a lot better, and are often easier to explain and remember, if you understand their rationale.

I completely agree with you. I just think the linked article could be strengthened by adding some rules of thumb. No complaints about its technical explanation.

I see what you mean - those rules don't help with writing code.

> At this point, you are probably thinking: “Holy cow! All these rules, and he is not even done with Use Case 1!”

No, I'm thinking "Holy cow! He used so many words to explain such simple behavior!"

The initializer list rule seems like a bad idea, at least.

'auto' is a terrible choice of the keyword. 'var', like in C#, would be much, much better. But no, the C/C++ people have been bitten by this weird keyword overload bug - the same one that caused 'static' to also mean "local"...

The issue is that 'var' would make old code not compile anymore, while 'auto' was already a keyword with pretty much no use.

Exactly; 'auto' was the corollary of 'register'. Ie. it specified that the variable was to be put into automatic storage. On most architectures this means the stack. So not only did it specify what was the default behavior anyway, but it's better to let the compiler figure that out too.

The change of the meaning of 'auto' already broke the old code. In C, 'auto' is used to specify a storage class; in C++, it stands for the type. So, the valid C code

  auto float x;
when compiled with a C++ compiler, produces an error message.

If we're talking about actual C++ code, auto is a reserved keyword (so it wouldn't be used as an identifier) but it was useless, so it wouldn't be used as a keyword in any real (C++) code.

That was virtually never used in the wild, though.

The real problem here is that keywords are in the same namespace as identifiers. If keywords were marked syntactically in some way and in their own namespace, there would be less reason to avoid introducing new ones. This is why we end up with _Bool, _Atomic, _Complex, etc., with hacky macro aliases. This is a problem in nearly all programming languages I'm familiar with.

> 'auto' is a terrible choice of the keyword. 'var', like in C#, would be much, much better.

I don't know; "auto" to me means "automatically deduced". While "var" sounds like it should apply only to variables, and auto to types (including return types, argument types etc).

The reality is it could have been called "floog" -- after a while I don't even think of the English meaning of "if" when I write "if (foo)...", much less "for(...", and non-english speakers simply learn the keywords by rote, much as non-Italian speakers learn western musical notation by rote (or do you silently translate "fortissimo" in your head as you play?)

#define var auto

That's a slippery slope.

auto can be ambiguous:

   template<typename Collection>
   void foo(const Collection &c) {
     auto x = c.find("foo");
is x an iterator (Collection is std::map) or a number (Collection is std::string)? Here it is better to write out the iterator type if that's what you mean.

auto can also be downright misleading:

   template<typename T>
   void foo(std::vector<T> &c) {
     auto first = c[0];
`first` has type T except when T is bool. Gross. Better to write out the type T.

auto can simplify many cases, but please don't take Sutter's "almost always auto" suggestion. Keep your future code readers in mind and be aware of its pitfalls.

> is x an iterator (Collection is std::map) or a number (Collection is std::string)? Here it is better to write out the iterator type if that's what you mean.

The reader would be able to infer this from context (as is the compiler, so it's still type-safe).

> `first` has type T except when T is bool. Gross. Better to write out the type T.

That's a preexisting problem with `vector<bool>` much more than it is with `auto`.

Auto is nice to use, but I absolutely hate getting up to speed on a code base that uses it.

Funny, I feel the opposite!

I'm quite happy to ignore the type in most cases (especially in templates) when I see a line like auto dest = make_data_sink(....blah blah....);

At that point I am unlikely to even worry about the type.

This suggests you need a better IDE.

You are correct. I work in Sublime Text.

What a terrible title.

My poor monkey brain couldn't cope with the amount of negation, and thought it's a criticism of "auto".

'auto' makes it difficult to predict what type you actually need for a new method that uses some parameter you get from a 3rd party library. I once had like 12 nested templates revealed to me only during debugging with RTTI as a type of some innocent auto parameter.

This trick from Effective Modern C++ has been a lifesaver:

template<typename T> class TD;

TD<decltype(YourTemplatedAutoClass)> td;

Compiler emits deducted class because TD is declared but not defined and can't be used.

This doesn't sound like a case where auto is the one making things harder...

"Effective Modern C++" (Scott Meyer) has a chapter on how to tease deduced types out of compilers. You can argue "it's silly that I even have to do that", and yes I'd argue that too, but sometimes you just need auto inside templates. Before I started using it, I thought it'd be a train wreck, but now I quite like it - when used judiciously.

I do find myself writing code with auto, then when I revisit it at some point, replace it with the explicit type for readability. But that's only for a small percentage of the cases where I use auto, so overall it's a net plus.

> ... when used judiciously.

That applies to many C++ features, not just auto.

Maybe the problem with C++ is that it's not a language that you can use well without developing a sense of good taste - knowing when to use certain things, and when not to. (And then you have to interface with code written by someone else who didn't have any taste, and just used features without regard to whether they made things better or worse...)

Pff, is that a problem with C++ or with programming in general? There is not a single language in the world you can use well without developing a sense of good taste. And what is good taste not only differs between languages (for the exact same concept, like how do you sum the values of an array, or how do you execute two code paths in parallel), but also changes over time.

That applies to any production language that has been in the wild for a few decades.

I never use auto in C++, except for when I have very complicated (or impossible to define) types like lambdas. I want complete clarity about what types I am using.

If you're still using c++ in 2017, you're already doomed. While I understand not everyone is pro enough to translate old rotting code to Rust and other superior languages, you still can make your contribution to humanity by writing new projects in better languages. For how many upcoming years will you fool yourself writing in shitty languages and calling yourself programmers while inflicting pain upon next generation of programmers lol.

Yes, because C++ isn't good enough to handle complex projects that require performance AND safety, few bugs. Like the whole F-35 platform. Hardly old and rotting. If you know what you're doing you can write safe and performant code using this language. I'll let you know though, it's a trick not that many people know how to perform, and it's not a fault of the language, but bias and misconception. But sure, for the next JSF program, let's just try something new and immature.

It's weird how people can't fathom that we're not all using their favorite language for real-time, safety-critical systems. Don't you think it's with good reason? That, in billion-dollar projects, all options have been considered? Everyone is using C and C++ for this purpose. From BAE to JPL.

Large scale projects aren't always about technology.

Hiring process, politics and developer salaries also play a big role.

Doesn't change the fact that no other language than C and C++ can be used for these systems. Same for complex and demanding video games, which has a lot of the same requirements and characteristics as safety-critical real-time systems. Plenty of game devs dislike C++, but right now, nothing else is feasible. One of these devs is Jon Blow, who's apparently so fed up with the language that he's put a lot of effort into one of his own. Several big industries use C and C++ because there is no other viable option. One day, other languages might be, but that day is not here.

Go ask the Rust or D guys if their language and tools are ready to replace C++ for the F-35 project.

Ada, SPARK and Real Time Java are used on those systems as well.

Regarding games, most AAA game engines are hybrid, with C++, C and Assembly only being used for the graphics and audio processing core.

Everything else on the engine is a mixture of D, C#, Blueprints, Lua, Python, Flash, Lisp, in-house scripting language, depending on the studio.

Ada usage has declined a lot. F-35 is primarily C++, Ada percentage is meager, and I haven't even heard it confirmed that Java is used for anything critical, but I wouldn't be surprised if it's used for some ground control stuff. NASA uses Javascript for a lot of their non-critical systems. Ada was highly specialized, and its use came with strict requirements (e.g., processor type) that have made it difficult to continue its use, not to mention lack of knowledgeable developers. In another turn, less strict DoD requirements have made it easier to use other languages. It's been quite a few years since Ada saw heavy usage in DoD projects, though it still has relevance. The legacy code base is huge.

I never meant to imply that games are comprised of only C++. Of course, you are right: a game and its tooling consist of several languages. Unreal Engine's build system is C#--but as you say, the core systems which makes sure everything's executed in time, rendering, physics, audio, are C, C++ with a bit of assembly.

And look at what Unity has to do with IL2CPP to increase performance. Unity has terrible performance because of Mono, but their work on the compiler and job system should start to pay off. In the case of Unreal Engine, you gain a lot of performance by using C++ instead of Blueprints. Even so, Blueprints already compile to bytecode and is ran in a VM and from there they call native C++ functions, they're not interpreted as is. Not that that has much relevance to what we're talking about, perhaps, but it does show that making a system like Blueprints is a significant performance compromise you always try to make up for. Epic recommends you use Blueprint Nativization (generating native code from your BPs) for increased performance, and just use C++ outright for heavy lifting parts. It's not without reason that Epic decided to drop UnrealScript and just use C++ as their scripting language, as well.

This comment came to be about Unreal Engine. I don't want to detract from Blueprints though; they're great at what they do. And not to be thought of as just visual scripting for people who can't program or something for only your artists/designers to use. A mistaken thinking many seem to share. It's best to use a combination of the two for easy gameplay scripting and iteration. Just write C++ and expose it to your BPs.

user: jkforpres created: 17 minutes ago

Didn't even want to use your normal account? Way to stand behind your inflammatory comments.

The truth is always bitter my friend. You could've made an argument against my cases but instead you chose to derail the discussion lol.

Applications are open for YC Winter 2019

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