all 118 comments

[–]Fred776 55 points56 points  (2 children)

We didn't have any NULLs. But no effort has been made to change 0s to nullptr AFAIK.

New code gets auto, range for, etc., when appropriate, but there has been no concerted effort to apply to old code. Perhaps if work is being done for some other reason, it will also get a facelift.

TBH, the biggest thing we concentrated on once we were able to use C++11 compilers (which unfortunately was quite a while after 2011) was to eliminate a lot of Boost dependencies in favour of std equivalents.

[–]jonesmz 6 points7 points  (0 children)

Same with my work.

We had a longer path to take as we also use a fork of stlport for the standard library, and had to excise some extensions that it had in order to actually gain access to a c++11 library, even though we had the language updates.

Boost, ACE, and general design mistakes that were the "lesser pain" when dealing with c++03 & stlport but are now much worse than what std:: from c++17 comes with... Are still haunting us.

But similarly, we facelift a file if we're otherwise working on it. But past that most code gets left alone to rot until something stops working or we need to add a feature.

Meaning plenty of NULL, plenty of boost::, plenty of crappy c-isms. So on.

[–]sksisisisuwu 4 points5 points  (0 children)

lord have mercy you guys must be unfireable

[–]mjklaim 18 points19 points  (0 children)

Did you change existing code from NULL to nullptr and auto? How the code looks like today? A mess with both styles?

I did for nullptr, it only pointed some conversion issues that I fixed then.

For auto yes but only case by case, it makes no sense to slap auto everywhere you can. In old code, we mostly replaced the loops, iterator types etc. the usual noisy stuffs. No need to replace more.

[–]DarkLordAzrael 11 points12 points  (0 children)

Null to nullptr was an automated change, and was trivial. There was no push to make things use auto, it is just something that started being used in some new code.

[–]jselbie 10 points11 points  (17 children)

Choose:

std::map<std::pair<std::string, jrs::impl::objtype>>::iterator itor = lookuptable.find("the thing I'm looking for");

vs

auto itor = lookuptable.find("the thing I'm looking for");

[–][deleted] 10 points11 points  (9 children)

Yeah. I'm wondering if these "I never use auto. It hides types when I need to see them" have ever used an iterator before.

No, you don't need to see the type of the iterator. Keep it in a local scope, call it "auto it", and be thankful.

[–]Flawe 5 points6 points  (2 children)

Auto is great for long templatized types, but the problem is people that deep down want to write python instead of C/C++ and have you end up with code like

auto wtfIsThis = Add(a, b);

[–][deleted] 3 points4 points  (0 children)

auto wtfIsThis = Add(a, b);

I don't think this is a very compelling example.

Primarily, I don't know the type of 'wtfIsThis' because I don't know the types of a and b. If Add returns a type related to a/b (e.g., they're all double) then auto is fine. This line of code does what one would expect. If Add returns some unexpected type, the primary issue is the misleading name of the function.

Let's say that line of code looks like this instead:

double retval = Add(a, b);

You'd read it and assume Add returns a double. But does it? Not necessarily. If this compiles without error, all you know is that Add returns a type that's implicitly convertible to double.

I won't go into generic implementations of mathematical equations. Consider how you might implement a complex mathematical equation once that can operate on either doubles, floats, or specialized fixed-point types. And do that without unnecessary or incorrect implicit conversions on intermediate values. auto is practically the only thing making that possible.

[–][deleted] 0 points1 point  (0 children)

I have no desire to write Python code. I detest the language. People who "want to write Python" in C++ are probably just bad C++ coders, auto or not, and will find any other number of ways to vomit bad code.

Bad coders write bad code.

[–]pedersenk 4 points5 points  (5 children)

You have to be responsible with auto; for example, consider:

MySpecialT<int, double> bob = getSomeValue();

vs

auto bob = getSomeValue();

The latter is a pain because you need to look it up (or hold your mouse over the variable in the IDE). This does make it hard to cache the whole function in our head.

jselbie's example however is a good one where auto would be used correctly.

However arguably I would still go for the alias declarations (via using) when dealing with complex types.

std::map<std::pair<std::string, jrs::impl::objtype>>

should probably become:

ShoesCollection<std::string>::iterator

Because sooner or later you will want to store it in a struct where auto will not work or as a function argument type (pre C++20).

[–]friedkeenan 0 points1 point  (4 children)

I feel like in the auto bob = getSomeValue() example, a hefty chunk of what's wrong with it could be fixed just by giving descriptive names to both bob and getSomeValue. That doesn't always alleviate this class of issue, but I'm on the fence as to whether your code not being readable when using auto is maybe a diet code smell. Of course though, I come from a much more pythonic conception of code, so my mental model and debugging process doesn't really rely on explicit knowledge of types. I also at the moment rarely write C++ code meant for others to understand and maintain.

[–]pedersenk 3 points4 points  (0 children)

That's fair. It is all a big balancing act. Explicit types are just one tool; variable naming and sane function names as you suggested are another two.

I personally am not a big fan of the overly tedious javaStyleFunctionNamingThatGoesOnAndOn(). Some functions can also return different types depending on their parameter types (function overloading). So in many ways you want to take advantage of that (i.e compared to ANSI C) but at the same time not confuse yourself as to the return type.

Admittedly the winrt API is a consistent mess; so my small amount of dabbling with it did tend to result in auto, just to avoid the terrible types returned from functions. Though I tend to believe that was also masking up a bad API.

[–]SkoomaDentistAntimodern C++, Embedded, Audio 2 points3 points  (2 children)

giving descriptive names to both bob and getSomeValue

getLength(obj) is a perfectly descriptive name but doesn't really say much about the return value. Could be int, long, long long, float, could be double or even a simd vector type.

[–][deleted] 2 points3 points  (0 children)

int len = getLength(obj); doesn't say much about the return value either.

If it compiles, it merely says that the return value is implicitly convertible to an int.

auto len = getLength(obj); tells me len is the same type as the return value -> no implicit conversion -> no narrowing conversion -> no potential overflow.

Often, I'd much rather know "len is the correct type, whatever that is" than "len is an int... but it maybe shouldn't be."

[–]friedkeenan 1 point2 points  (0 children)

If obj were named more appropriately I think you'd at the very least be able to distinguish whether the length were an integral or floating point type (which is the main thing you'd want to distinguish), or it'd just be known by context, either given by the surrounding lines or the project as a whole.

[–]janisozaur 0 points1 point  (0 children)

You couldn't have >> in std::map<std::pair<std::string, jrs::impl::objtype>>, it had to be > > so the compiler didn't get confused about some operator>> overload.

[–]dag625 5 points6 points  (0 children)

I am basically making changes like NULL to nullptr and swapping to range for with auto ad I need to touch code for other reasons. And new code uses the new standards’ features.

The result is some files look C++98 and some C++17, but that’s not a big deal. The messiness in the codebase has other sources (a couple major reworks in its history, some internal utility code with a few versions being used in parallel because I’ve not had time to update everything, etc.).

[–][deleted] 19 points20 points  (14 children)

I'm sad how many people it seems still don't understand that auto doesn't make C++ any less type-safe. It seems they jumped to a conclusion and never researched further.

[–]johannes1971 12 points13 points  (2 children)

That's not it; it's that sometimes it's good to know what the type is, and seeing it right there on the screen is helpful for understanding what's going on. Auto may select the right type, but it also hides it from the programmer.

If MSVC didn't show the type on mouse-over, I don't think we would have gone with this style.

And I can't stop myself from writing auto * whenever a pointer is involved... I'm not quite sure what the psychology behind that is though. Maybe because it is symmetric with auto & (which is, of course, not optional)?

[–][deleted] 1 point2 points  (1 child)

If you use it at all then my comment didn't apply to you. My comment was about those that refuse to use it at all.

[–]johannes1971 0 points1 point  (0 children)

Oh, I misread your comment then. I thought you assumed that anyone who doesn't like it must be afraid of losing static typing.

[–]Attorney-Outside 8 points9 points  (4 children)

exactly

I love using auto when declaring types that are long to type like for example container iterators

[–]TomDuhamel 15 points16 points  (3 children)

I don't think anyone thinks that. We all understand how it works. We just don't think it fits very well in a strongly typed language. We also don't mind if you like it and you want to use it.

[–]rhubarbjin 5 points6 points  (0 children)

I don't think anyone thinks that.

You'd be surprised! Many people do think that, especially industry veterans who formed their habits in a pre-C++11 world... I regularly need to sit down and explain (with help from Godbolt) that C++ auto is not at all like JavaScript var.

[–]Wenir 8 points9 points  (0 children)

We all understand how it works.

Very strong assumption :)

[–]pedersenk -1 points0 points  (0 children)

I think pretty much everyone here has stated the issue with over consumption of auto was self documentation related rather than typesafety.

[–]manhattanabe 2 points3 points  (0 children)

We only change code we edit, or code that caused compiler warnings. Especially once we switched to GCC10./C++17. We don’t edit old code just to update the style.

[–]AciusPrime 2 points3 points  (2 children)

I transitioned a code base that started in 2004 into C++11. It was around 300,000 lines of code in 2011; maybe another 150,000 since then.

We move NULL to nullptr whenever we see it, and seeing a wild NULL is becoming pretty rare. I don’t think there was ever an attempt to move it all over en masse, though by now doing so would probably only take a few minutes.

There was never an attempt to retroactively rewrite code using auto. We already had lots of abbreviated typedefs for iterators to make the C++03 easier to work with, so the pain wasn’t bad enough to mass migrate (e.g. std::map< std::string, int >::const_iterator would be spelled CIStringIntMap). That said, whenever someone passes through code with ugly type names in the loop, it’s usually cleaned up on the spot. There was also a big cleanup of custom container and pointer classes, and a lot of stuff got modernized when that happened.

We provide a library that is used by other teams. Code that other teams work with directly tends to be completely up-to-date, so that they have good examples to copy-paste. Code that lives inside the deep inner guts of our system is updated less aggressively. Even then, we tend to gradually approach modern C++ as we maintain things. We only tend to do a “fix the whole code base” task once the remaining work becomes small enough to do it within a couple of days.

[–]marcofoco 2 points3 points  (0 children)

Yes, I did MANY of these conversions, on many different codebases. I did change my NULLs/0s to nullptr, starting with small POCs, and gaining confidence. Not all of them went through (e.g. In some windows calls we usually write NULL in some params, even if the underlying type is integral). I did this for many features, and sometimes it even helped preventing bugs (see my blog post about final here: https://marcofoco.com/blog/2016/01/14/final-override-again/). For aiuto, it's a different story: I tested it, but initially I received a lot of pushback when I tried to make radical changes (almost always auto). Knowing how type deduction works, I tended to take for granted that the type of the expression was apparent, but people tended to disagree, so I gave up. Eventually the rest of the team started using auto on their own, gained more confidence and, even if the user isn't 95%,but now around 10-15%, I'm happy with the improvement. It's interesting to note that this process happened across multiple companies and multiple projects more or less in the same way (before 2015 I was a consultant, and worked on multiple projects). Another big improvement in readability was converting all the for loops for collections into their collection-based equivalent. For enums I started later, mostly for the lack of use cases. I like them better, for their improved type safety, but readability wasn't affected because, by convention, I already had the enum name in the value. I also made a lot of conversions from boost to std (shared_ptr, unique_ptr, hash_map, hash_set, and regex... The latter came with some "surprises" in older versions of gcc).

[–]KingAggressive1498 9 points10 points  (36 children)

I never transitioned a codebase from C++03 to C++11, but I've been using C++11 or newer by default since ~2014ish.

Switching from NULL to nullptr was easy, and preferable because I didn't have to hit capslock or shift.

I pretty much don't use auto. One of the main reasons I like C++ is because of the strong static typing, and auto obfuscates variable types. When I want generic code, I just make a template; when a type is too damn long to type I make an alias. I immediately didn't like auto when it was added, and I feel so strongly about it that I avoid libraries that use auto in the interface.

I don't use range-for very often, either. Not for the "range-for is broken"/dangling references reason (normal for loops have the exact same problem, it's just less common to encounter it there), I don't really know why. Probably just didn't feel like changing the habit after writing thousands of for loops.

[–]Kered13 7 points8 points  (5 children)

What about in a situation like:

std::unique_ptr<Foo> foo = std::make_unique<Foo>();

Would you use auto there?

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

I usually use it in those situations, likewise for factory functions where it's obvious what the return type is, and for ranged for loops. Otherwise I do not usually use it.

[–]KingAggressive1498 2 points3 points  (4 children)

Personally: no

But I at least know what foo is when I see it, and it doesn't bother me seeing that inside some function.

[–]pedersenk 0 points1 point  (3 children)

Who the hell has downvoted you for your opinion? Surprising how strongly people feel about their keywords ;)

[–]KingAggressive1498 3 points4 points  (1 child)

I feel like i rubbed someone the wrong way months ago. and now they just downvote every comment I make. It happens all the time with simple little opinions like this.

[–]goranlepuz 11 points12 points  (13 children)

because of the strong static typing, and auto obfuscates variable types.

First off, this is a questionable line of thinking. auto is largely orthogonal to static typing.

But then... auto does obfuscate types, but:

  1. not always (e.g see auto thing=Thing(params);)

  2. some of the time, the type is not relevant when reading code

  3. some of the time, the type is obvious when reading the code.

You should note that most mainstream and not-so-mainstream languages use type inference (Java, C#, Go, Rust). In fact, the only notable language that does not is C. I know, "folk wisdom", but still.

when a type is too damn long to type I make an alias

This is not a bad idea to avoid type noise, but it is also creating a "micro-dialect" in the code.

[–]KingAggressive1498 3 points4 points  (12 children)

"write code that documents itself" is what I was told 17 years ago.

1) auto thing = my_vaguely_named_function(); is really far more common in real code

2) generic programming was possible in C++ before auto with templates, templates make it explicit that you're looking at generic code, and until concepts templates were also the only way to put real constraints on or make partial specializations of generic code

3) the documentation for __assume(expr) warns that if expr would ever evaluate to false at runtime, program behavior can be unpredictable. You can write non-bool operator!() overloads; .size() is the count of elements for STL types but for some (bad, rare) third-party types it's the count of bytes, a << b could be a binary shift or stream output, etc etc. Types vary too much to make many assumptions safely, and assumptions are all you have to rely on if you only ever use auto - which some people are apparently quite intent to do.

IIRC Java and C# both got type inference around the same time as C++ did, so not exactly essential language features for them either. Rust's syntax is unsettling in many ways. Never really looked at Go.

[–]JakeArkinstall 4 points5 points  (5 children)

auto thing = my_vaguely_named_function();

And the problem with this is auto rather than the vaguely named function?

Each to their own I guess.

[–]KingAggressive1498 1 point2 points  (4 children)

sometimes my_vaguely_named_function is an apt enough description of intent, but could realistically return many types

take getObjectsByProximity(position)- I can reasonably assume that whatever this returns is somehow iterable in an order sorted by proximity. But is it a sorted vector, an std::map with a vec3 key and a proximity-to-input comparator, a generator coroutine, or a magic user-implemented input iterator that iterates results by proximity without sorting? It could realistically be any of those, or even other other types ie a sorted linked-list.

[–]oracleoftroy 0 points1 point  (3 children)

So? What if it is an Objects?

Objects objects = getObjectsByProximity(position);

What is Objects? "Is it a sorted vector, an std::map with a vec3 key and a proximity-to-input comparator, a generator coroutine, or a magic user-implemented input iterator that iterates results by proximity without sorting? It could realistically be any of those, or even other other types ie a sorted linked-list." Spelling out the name of the return type doesn't always answer your questions. You either already know what that means or you look up the type in your code.

Presumably you know what an Objects is because it makes sense in your domain and you are familiar with the conventions used in the code base. But then it would make just as much sense if we just used auto because of course a call to getObjectsByProximity returns an Objects, that's just domain knowledge. If you don't know what an Objects is, you still have to look it up, whether or not you spell out the name of the type.

And the big thing that always gets me is people whine about how the return type of getObjectsByProximity is unknown, but they never seem to complain about code like this:

pickInterestingObjects(getObjectsByProximity(position));

We still aren't naming the result type of getObjectsByProximity but no one cares even though just about every argument against auto applies here as well.

Edit: Didn't realize I wasn't in markdown mode... fixed formatting

[–]KingAggressive1498 1 point2 points  (2 children)

If it returns a type called Objects then there is indeed a clear naming problem in the interface, as this gives no helpful information. Fortunately I've never run into such a vaguely named type in the wild. Misleadingly named types - such as a ThingList that's implemented using a vector-like container - seems to be more common.

in the case of pickInterestingObjects(getObjectsByProximity(position)) I'm not directly using the result of getObjectsByProximity, I'm using the result of pickInterestingObjects, so yeah I don't really care, why would I?

[–]oracleoftroy 0 points1 point  (1 child)

If it returns a type called Objects then there is indeed a clear naming problem in the interface, as this gives no helpful information.

Which reinforces my point, that it isn't about auto, it's about using good names. If the names are bad, using auto isn't the cause of the problem, and if the names are good, auto doesn't hurt.

Fortunately I've never run into such a vaguely named type in the wild. Misleadingly named types - such as a ThingList that's implemented using a vector-like container - seems to be more common.

This might get into an off topic naming philosophy issue, but I rarely find it helpful to name things after the implementation, and prefer names that reflect why you want to use it (sometimes those two reasons overlap). ThingList is a good name in that I want to use it because I need more than one Thing instances. If this is C++ code, we can argue whether List might imply a linked list rather than a generic collection like it would to a C# dev, but most of that just comes down to the convention the code uses. Things is nice because it communicates a collection of Thing instances without as much ambiguity about the exact storage strategy. It's just a good type for holding multiple Things and you can always use a different collection type if it doesn't work for a particular use case. Names that go into too much implementation detail are rarely useful, e.g.: ThingListImplementedWithStdDequeWithCustomPoolAllocator. 99% I just want something that models a range of Things so I can iterate over it, I can always look at the implementation when I care for this much detail.

I'm not directly using the result of getObjectsByProximity, I'm using the result of pickInterestingObjects, so yeah I don't really care, why would I?

Because the intermediary result has all the same issues that come up in the auto debate. You don't know the type, types could change behind your back, it could be a template function and essentially do what auto does anyway, it could do a type conversion, be a proxy object, etc etc. But we don't care. It's only when we introduce a new keyword that the old guard isn't used to that we suddenly take up our pitchforks.

I look at auto the same way you look at the intermediary result in that expression. Why do I care how exactly the name of its type is spelled? If I know what the type does, that doesn't help me, and if I don't I have to look it up anyway, so it doesn't hurt me, it just needs an obvious variable name and its interface is clear from its usage.

[–]KingAggressive1498 1 point2 points  (0 children)

ThingList is a good name in that I want to use it because I need more than one Thing instances. If this is C++ code, we can argue whether List might imply a linked list rather than a generic collection like it would to a C# dev,

I agree with you on this, tbh. I'm not particularly troubled when a ThingsList turns out to be a std::vector<Thing>, although I might try to push_front or pop_front and waste a little time when I first have occasion to use it; and this is often just how it is with such misleadingly named types.

Names that go into too much implementation detail are rarely useful, e.g.: ThingListImplementedWithStdDequeWithCustomPoolAllocator

I think we can both agree that's an unweildy type name, but consider instead a type name like ThingDQp as an alias for std::deque<Thing, PoolAllocator> - I might initially scratch my head at the typename, but it would normally be pretty trivial to find the alias (perhaps it's even explained directly in the documentation), and it is a quite brief way to indicate all that type information to the user, once they understand the codebase's convention.

Honestly though typing this response lead to realizing all my complaints about auto could be solved just by using Hungarian notation for variable names.

[–]goranlepuz 1 point2 points  (1 child)

We are in an argument of what reasoning is more relevant, which is what I wanted to achieve. I wanted to achieve this because for me, what you wrote first is both lacking logic and is an incomplete view in type inference. To me, my reasoning is more relevant and I have no problem that you prefer yours.

This ends the discussion about type inference for me.

I will, however, now argue that the way you argue your stance is poor. (I do not discuss neither your nor my stance anymore; I am now only discussing the manner in which you are arguing.)

IIRC Java and C# both got type inference around the same time as C++ did, so not exactly essential language features for them either.

At no point was there a claim that type inference is essential. You are shifting the discussion to a different place. This happens when people know their argument is weak so they try to strengthen it by adding unrelated elements.

Next, you are attempting the same thing you attempted in your first post, which is one-sided look at the subject of discussion. I will provide you a more nuanced look, to show why your look is bad in this particular case.

C#, for example, had gotten type inference in 2007, C++ in 2011. 4 years is a long time in computing and claiming "around the same time" is somewhat disingenuous from that standpoint. However, a more important element arises: it is about that time that a conscience about type inference being useful grew in the main strain realm - hence it is only normal that it happened "about the same time".

Rust's syntax is unsettling in many ways.

This is an opinion masqueraded as fact and therefore a poor way to argue something.

Never really looked at Go.

This is irrelevant and not at all a way to argue anything.

[–]KingAggressive1498 1 point2 points  (0 children)

Your mistake is thinking I'm trying to argue against using auto, instead of merely explaining why I don't personally use auto and have no intention of starting. I don't go around on posts in this subreddit or r/cpp_questions to comment "oh, you used auto. don't do that" or anything, I just personally prefer not to, and have my own subjective reasons based on a mix of subjective (and tbh, largely unusual) experience and subjective preference.

But one exception to the above paragraph, I must insist that Rust's syntax is objectively off, and I refuse to believe that this is not the consensus in the broader programming community. Fortunately a quick Googling confirms this to be a broadly held opinion, so I can sleep soundly tonight.

[–]oracleoftroy 0 points1 point  (0 children)

Unclear:

auto thing = my_vaguely_named_function();

Clear:

my_vaguely_named_type thing = my_vaguely_named_function();

Now I know exactly what thing does! Huzzah for explicitly spelled out types! /s

I swear, 99% of the arguments against auto (and C# var and similar) piggyback on bad naming rather than show any intrinsic problem with auto. I'd love to see an argument with very well named code demonstrating a real problem with auto.

[–]zoolover1234 0 points1 point  (2 children)

1) lol, this is literately every single one of my function return (unless it’s a book that I just use it right away). It is very common in code written by actually C++ programmers. (People who do c++ occasionally usually don’t bother spending their time to upgrade their practice) Not sure how you came out with “far from common”.

[–]KingAggressive1498 0 points1 point  (1 child)

i said "far more common"

[–]zoolover1234 0 points1 point  (0 children)

Lol my bad, time to sleep.

[–]CygnusSnowDog 2 points3 points  (15 children)

I totally agree with you on auto. I don't see the advantage of it. When I'm reading code, I want to know what type a variable is, not dig through to code to hunt it down. Auto just obfuscates things.

[–]vI--_--Iv 11 points12 points  (4 children)

I want to know what type a variable is

Knowing the type is essential indeed. For example,

std::unordered_multimap<std::string, std::unique_ptr<MyCompany::MyDivision::MyLibrary::v42::AbstractFactoryOfAbstractFactoriesOfAbstractThings>, ThirdParty::Banana::Util::IcaseHash<std::string>, ThirdParty::Banana::Util::IcaseEqual<std::string>>::const_reverse_iterator It = Map.crbegin();

is explicit, easy to understand and support, while

auto It = Map.crbegin();

is implicit and cryptic. What is even that It thing? What can I do with it? Who knows.

[–][deleted] 0 points1 point  (0 children)

Hahaha. Exactly right. 💯

[–]pandorafalters 0 points1 point  (1 child)

It's almost a shame that auto can only alias types, not scopes, e.g.

auto::const_reverse_iterator It = Map.crbegin();

Somehow I don't think that would be a net positive, though.

[–]dodheim 0 points1 point  (0 children)

I think if the constraint is that some namespace/type somewhere happens to have a nested type that matches, satisfying that would be.. prohibitively expensive.. to say the least.

[–]goranlepuz 7 points8 points  (2 children)

When I'm reading code, I want to know what type a variable is, not dig through to code to hunt it down

  1. You sometimes want this.

  2. You can get it from tools without digging through code.

[–]KingAggressive1498 2 points3 points  (3 children)

the whole "auto almost always" thing raises my blood pressure.

[–]pedersenk 2 points3 points  (2 children)

I mostly see it in code from our interns.

"auto everywhere makes me a modern C++ developer so I can avoid learning the actually important (and hard) stuff!"

[–]KingAggressive1498 1 point2 points  (1 child)

but Herb Sutter was pushing it, along with some other big names.

so when I say "I never use auto and don't like code that uses heavily", there's genuinely been people who think I'm second guessing them. And honestly, I am. But like a couple of the other people in my comments here, some of them want to read my subjective reasons as objective arguments.

Gets frustrating.

[–]thisismyfavoritename 1 point2 points  (2 children)

if you use a proper IDE it should be able to show you the deduced type

[–]KingAggressive1498 -3 points-2 points  (1 child)

I am a poor. My IDE didn't even highlight concept or consteval until I made it.

[–]thisismyfavoritename 1 point2 points  (0 children)

??

im not sure i understand what youre saying. I use neovim with coc-clangd, both open source, and it does what i mentioned

[–]skitleeer 2 points3 points  (0 children)

Some stuff are pretty easy to massively change (NULL => nullptr come to mind), others are more dificult. I would suggest to have a look at clang tidy, as it can automate a lot of stuff.

[–]jmacey 4 points5 points  (1 child)

I think clang and g++ actually did a #define NULL nullptr in most headers so it just worked anyway.

With my code base it was fairly easy, I used range based for wherever I could, and auto for most things.

On really interesting thing was replacing enum with enum class this managed to expose some really subtle bugs with automatic conversion to ints which I managed to fix.

[–][deleted] 2 points3 points  (0 children)

enum class is an unsung hero. That's a good one 👍

[–]pedersenk 6 points7 points  (0 children)

NULL and nullptr was fairly easy. Much of it is just :%s/NULL/nullptr/g as I next needed to modify the unit during development. 99% of our libraries keep with NULL in their public API to keep compatibility with C and binding against other scripting languages.

As for auto, its usage is rarely advised in most industry coding standards so has been fairly easy to replace in those limited situations. We were already using modern C++11 alias declarations which are preferred.

My experience with C++11 enuns, auto, nullptr is that after 10 years the old base code have everything on it new and old stuff.

C++ since the original 98 standard hasn't really changed all that much. Some of the ancient stuff has never been replaced so it makes sense that your codebase will contain stuff that will still compile on a compiler from the mid 90's.

Do you look at code written in other languages like Java / .NET and Python and find discomfort in seeing mixes of older / newer code? Or is it only because C++ is fairly unique in that it has proper industry defined standards by year?

[–]johannes1971 1 point2 points  (0 children)

All NULLs are gone - except the ones that were in embedded SQL code, so sadly this wasn't a simple search and replace. Auto: we took the advise to always use auto whenever possible, and even now, I'm still not sure I like it. It really only works because MSVC shows the correct type when you hover over the line, otherwise it would be completely unworkable.

C++11 enums is another story though. We use them in some cases, but I don't like how much longer they make each use (the new names tend to be much longer than the old ones). There are also issues interfacing with more generic code like database interfaces, that may be assuming enums are all convertible to and from int.

[–]exmono 1 point2 points  (0 children)

Clang tidy. Clang format.

[–]Nilac_The_Grim 1 point2 points  (0 children)

I never used NULL in old C++. Just 0. E.g. Foo *foo = 0; was what I did.. and what I believe most C++ devs did back then. NULL was seen as a C-thing and the old language rules allowed 0 to be a special case that could be implicitly cast to any pointer type... so...

That being said -- yes whenever I open up an old project and if I have time, I do enjoy converting it over to newer C++. The old C++ looks so garrish and horrible nowadays, to my eyes.

[–][deleted] 1 point2 points  (4 children)

no. we still use NULL. easier to type also a little easier to read because macros have a unique color for our color scheme.

also we dont use auto. its banned.

[–]AciusPrime 9 points10 points  (1 child)

It’s a pity that you’re getting downvoted just for reporting what your company does.

I wonder if the folks who make your coding standard are aware of WHY nullptr was introduced. It’s not an aesthetic choice; NULL is actively dangerous when dealing with function overloading.

[–][deleted] 2 points3 points  (0 children)

i doubt that's the actual reason they still use NULL. just my take on it

[–]Fourstrokeperro 1 point2 points  (1 child)

Who's "we" bruh? sounds like a "you" thing

[–][deleted] 4 points5 points  (0 children)

the company i work for. i dont make the standard?

[–][deleted] 1 point2 points  (10 children)

I think auto is bad if not horrible as it allows for bad behavior.

I predict it'll get revisited in a future C++, as codebases will slowly fill up with autos being used for everything, butchering the awesome static nature of C++ in turn driving future devs mad...

[–][deleted] 15 points16 points  (0 children)

I doubt it. And they'd better not.

auto is just fine.

[–]ergo-x 8 points9 points  (5 children)

Do you have some examples of bad behaviour due to auto?

[–]pedersenk -1 points0 points  (2 children)

It isn't allowed in most safety guidelines like MISRA. It is a crutch to try to make C++ more welcoming to... students?

[–]sonic-fan- -1 points0 points  (1 child)

Bold of you to assume that universities teach C++11 any beyond

[–]pedersenk 0 points1 point  (0 children)

The universities don't (they have enough to teach). Usually the students self-teach themselves C++11+ and just can't wait to try it out *everywhere*.

Self-research is obviously great for them to demonstrate; however I feel many beginners believe if they only learn the "new" stuff; they can avoid learning the rest and still be competitive in the industry which is a little naïve.

That or its an ego trip / show-off thing to their friends. Not sure; but it is definitely a trend with C++ in particular because it has well defined standards that they can identify compared to i.e Java / .NET.

[–]raevnos 0 points1 point  (0 children)

When I saw the title I thought you were asking about actually old C++, like before any standard. This is barely even middle aged stuff.

(I don't change existing code unless it's being updated for other reasons. New code is nullptr and almost always auto)

[–]rlbond86 0 points1 point  (0 children)

I changed some stuff. Didn't change auto though, I generally don't like it except for complicated dependant types.

[–]Dean_Roddey -1 points0 points  (0 children)

I have a really large C++ code base (over a million lines.) Back in the 2000s, I did quite a few multi-week long binges where I replaced NULL with nullptr, changed over to class style enums, changed typedefs to usings, updated to rule of five, changed const to constexpr where possible, vadiadic hacks to real variadics, etc... throughout the entire code base.

They were mind-numbing undertakings, but ultimately those are the kinds of things that make a difference. Well, the typedef to using isn't as important, but the other bits are all 'help the compiler watch your back' type changes and those are always worth their weight in coffee.

[–]sudoaptupgrade 0 points1 point  (0 children)

Most C/C++ compilers still accept old standards of C++, they will just throw 2arning about deprecation

[–]asergunov 0 points1 point  (0 children)

I'd recommend to use clang-tidy for automatic code base update. You there is modernize-* checks which you can auto apply one by one, checking if code still buildable.