These samples do not appear in the paper, so we don't know what they saw.
The “iterators” discussed are Java-/C#-style iterators, not C++ ones (as I expected reading the abstract).
In a C++ context I would have expected lambdas vs iterators to be something like:
// lambda
float retVal = 0;
std::for_each(mb.cbegin(), mb.cend(), [&](item x) { retVal += item.price; });
return retVal;
// pure iterator
float retVal = 0;
for (auto it = mb.cbegin(); it != mb.cend(); ++it)
{
retVal += it->price;
}
return retVal;
... and the first would be better off as: return std::accumulate(mb.cbegin(), mb.cend(), 0f,
[](float acc, item x) { return acc + x.price; });
I think the need to use ref-capture (since you only get a side-effecting `std::function` to play with in their sample) would be the thing most likely to throw people off – as it’s something that should be avoided in most code, anyway ;)EDIT: Looks like you beat me to the punch on some of these ;)
Instead of:
float getSum(marketBasket mb) {
float retVal = 0;
// Implement solution here
// ---------
marketBasket::iterator iter = mb.begin();
while (iter.hasNext()) {
retVal += iter.get().price;
iter.next();
}
// ---------
return retVal;
}
I'd rather see real SC++L compatible iterators (as hopefully taught) and saner naming: float getSum(marketBasket market) {
float sum = 0;
// Implement solution here
// ---------
for (marketBasket::iterator item = market.begin(); item != market.end(); ++item) {
sum += item->price;
}
// ---------
return sum;
}
And instead of being pre-provided with a function <void(item)>, if I'm reading the pdf correctly: float getSum(marketBasket mb) {
float retVal = 0;
// Implement solution here
// ---------
function <void(item)> func = [&](item theItem) {
retVal += theItem.price;
};
mb.iterateOverItems(func);
// ---------
return retVal;
}
I'd rather see: float getSum(marketBasket market) {
float sum = 0;
// Implement solution here
// ---------
market.for_each([&](item theItem) {
sum += theItem.price;
});
// ---------
return sum;
}
Or venturing into the far more functional style, where lambdas start to shine for me, personally: float getSum(std::vector<item> market) {
// Implement solution here
// ---------
return std::accumulate(market.begin(), market.end(), 0.0f, [&](float sum, item theItem) {
return sum + theItem.price;
});
// ---------
}
It looks a bit better in C# where your selection of standard functions is a little less anemic and a little nicer to use: float GetSum(MarketBasket market) {
// Implement solution here
// ---------
return market.Sum(item => item.Price);
// ---------
}Shameless plug: http://duneroadrunner.github.io/SaferCPlusPlus/#msevector
I can't begin to stress what a huge timesaver it is being able to bind a button's callback to a quick lambda instead of having to bind a callback to an std::function, add the function to the class header, and then put the actual button-click code somewhere else in the project in a separate function ... and then repeat that process for a large UI with hundreds of widgets.
It's not even the initial extra typing, it's having all the code right where it's most relevant instead of scattered around everywhere and having to name all of these callback functions.
For example, consider callback heavy asynchronous code. A promise library with lambdas is much easier to write and read than the equivalent state machine.
I would go as far to say any mechanism where function chaining is useful, such as the nice data to mark/SVG abstraction used in D3, and also in promise libraries, has advantages with lambdas. Not only do you avoid having to write extra classes or methods, but the code is more succinctly logically grouped together, requiring fewer indirections to get to the transformations occurring.
It seems really dumb to me to declare that lambdas are detrimental to novices when it's clear they have a great deal of utility outside of something like replacing iterators.
I need the code to be SOLID, I need the time to market to be as small as possible and I need to be able to replace any developer with any developer on a moments notice.
When students don't know lambdas you're costing me money by using them, because you made the training process longer.
async/await makes this even easier, AFAIK C++ is getting it soon as well.
Also, their examples are removed from reality. I've never seen people use lambdas like that. Most of the use cases I've seen are callbacks that get triggered on certain events (i.e. "display notification when background loading thread completes") and predicates (i.e. find_if). I see neither in their examples.
Already in 1994 it was obvious to me that it leads to bad unsafe code and worse, the mentality to micro-benchmark each code line.
- if local variables need to be captured, the code is much less noisy than using the old std::bind mess
- non-capturing lambdas also work for C-style function pointers, and in this case the generated code is very efficient (no std::function object created under the hood)
If you're aware what happens under the hood (...that in most cases a std::function object will be created, which in turn might do a dynamic memory allocation to hold captured variables), lambdas are a useful syntactic sugar and one of the more useful additions in C++11 (IMHO).
As long as you maintain the distinct type of the lambda, any captures (by reference or by value) will just be fields in a stack object.
Which is not surprising at all, because C++ is a very complicated language !
It takes years, even decades before you can master this language - even then you probably won't be using all of its features.
Back in the day, it took me a long time before I could truly grasp the C strings and pointers in general.
Yes, I could write code with pointers and C strings (new char[length + 1] and delete[] str; were my buddies!), but sometimes I was programming with my eyes closed and fingers crossed, because the implications of sharing raw pointers were too complex, especially if threads were involved.
Same with these new features. They may 'look' more modern due to the updated syntax, but the underlying concepts that they are hiding still need to be well understood.
So not surprising that students struggle with getting it right the first time. The good news is that with a bit of experience this becomes easier, as with anything else..persistence is key.
As for your guesswork with C-style strings, you should have been using std::string and using copy constructors to pass work into a thread (eg. who owns the data the thread is working on?). With modern things you can move the data into the thread instead of copying it.
I would recommend students working on their own personal projects as a good pasttime as it further helps anyone understand how to write a decent program and learn the language. Also, tell them to read Stroustrup's book.
Standard library was, for one reason or another, unpopular back then. Or maybe because of the C Windows API and later MFC, which came with its own CString class...
After the standard including lambdas came out, compilers did not immediately comply to it, and it took some time for them to catch up. Then it took even more time for tutorials and books to catch up. And it will take time for the C++ community as a whole to catch up as well.
However, C++ lambdas picked the most horrifically ugly syntax possible, and they switch between three subtly different semantics (copy, reference, move) depending on a single glyph. I feel bad for the people working on modern C++ - maintaining backwards compatibility is a huge constraint upon design space.
I'm not surprised that lambdas slow down people that aren't already experienced with them. Since I rarely program in C++, whenever I go back to it, I always have to spend a bit of time bashing my head against the horrific syntax.
[foo=std::move(bar)]() {}It was ugly as heck and was removed as soon as I have c++14
I understand that C++ lambda syntax can be confusing on first encounter, but after reading about them for five minutes it should be very naturally comprehensible to any experienced C++ programmer. It's terse, but very clear.
When lambdas first came about there was a lot of "meh, I could already write a functor just fine" but the ease of using them is just so much nicer and the readability of the code is so much greater. Just writing a plain for loop would often be less code than using an STL algorithm and a custom functor so there was very little incentive to use the highly tuned STL algorithms if it was just going to take more boilerplate. Now there is and we are finally seeing C++ programmers catching up to the insights that functional programmers had made a long time ago[1]
Others have said this but comparing lambdas to iterators is a very odd decision because they are completely unrelated things.
The conclusion should really be that anonymous functions aren't as intuitive as "regular" imperative code. The regular code walks you through what is going on in babysteps so even a new user can figure out what is going on. The way with lambdas requires a bit more knowledge but for working programmers who aren't learning C++ for the first time they are obviously a big boon. Sometimes you have to learn something slightly more complex to reap the benefits of it. Pointers are very hard for brand new programmers to wrap their heads around but nobody would argue that judicious use of them can be a real benefit (or essential, even) in some situations. This is like concluding that smart pointers are bad because they are less explicit about what is going on. You still need to learn C++ manual memory management but your code will clearly benefit (both in terms of verbosity and memory safety) by using smart pointers 95% of the time.
1. CppCon 2016: Ben Deane "std::accumulate: Exploring an Algorithmic Empire" https://www.youtube.com/watch?v=B6twozNPUoA (std::accumulate, fold essentially, has been in C++'s STL since the beginning and Stepanov probably had it in mind before C++ even existed but we are just now getting lectures like this and I think lambdas are to thank for this)
I say this not to dismiss the study, which appears to be fairly well done and to provide interesting results. I'm simply saying that its results are not inconsistent with lambdas providing a net, long-term benefit if introduced to a development team at work.
> All the tasks focused on iteration on a collection using a C++ vector object.
That's comparing "functional style" collections to iteration.
It is readily apparent that C++ syntax is so troublesome that "functional collections" won't be a win for small iteration blocks.
We had the same debate for functional Java collection methods (although the Java 8 lambdas tilt it more closely in functional Java's favor)
The main use case for C++ lambdas is for declaring callbacks.
The paper seems to study the use of lambdas specifically as an alternative to iterators. I actually mostly still use iterators myself, rather than lambda-based alternatives, so I agree: they don't have a huge impact there.
But the headline is misleading. It should have had "when compared with iterators" added to the end.
Parser ParserGenerator::compileLiteral(Rule& rule){
const string literal = rule.value;
Parser parser = [literal] (shared_ptr<State> state){
//parse the literal...
state.Advance(literal.size())
return state;
}
return parser
}
Solving this without lambdas would require a different encapsulation mechanism for the rule data, such as a class, which (IMHO) would make the code less idiomatic.So lambdas are actually very useful!
PEGTL does this. Approximately, you have a template parameter MyAction<> on the Parser<> template which in turn calls "MyAction<Rule>::apply (state)" when parsing of each rule is complete.
It's a fantastic library. I highly recommend giving it a whirl.
[0] https://github.com/ColinH/PEGTL/blob/master/doc/Actions-and-...
It is fairly easy to implement and the compiler will inline the functor so there is no performance penalty. Any solution based on iterators that I can think of would be much more complicated and would probably not lead to as efficient code.
I'll admit that the "user interface" for iterators are much nicer than using functors. The former meshes well with c++:s for loop syntax while the syntax for lambda constructs is bad.
I prefer my computer languages to be like Chess - a few rules but efficient and expressive. Like C.
Why? Because everyone can read the code others on the team wrote, without being a language lawyer and knowing tons of esoteric features and magic.
That has implications for maintainability and team productivity, and the bottom line for a business.
Really, people, having basic coding style conventions instead of tons of language features wasn't so bad:
int Foo_bar(struct Foo* this) {
// even this is readable
}The more features a language has, the more I have to know just to read someone's code or be productive in a company - and the more chance someone doesn't know about potential harmful interactions and side effects.
As an example, you do not have to be a language lawyer to understand that C-style casts are horrible and dangerous, and really will lead to colossal implications for maintainability, team productivity and the bottom line for a business (it is a static_cast or reinterpret_cast? Who knows?!)
If you're employing people who only know half of the language or never finished chapter 1 of Stroustrup's C++ book, then I would say it is a problem with your employees than the language.
Typically, on monday mornings and friday evenings, he's very loud and looking busy. This is an age-old trick to superficially keep your job.
Ever so often one would hear his odd sounding voice pierce through the office talking about some idiosyncratic evidence while looking proud of himself.
Truly, if this guy was a programmer (which I doubt), he would be using lambdas. See, lambdas might have a use after all...
Really, would anyone expect lambdas to improve compiler errors? Also, isn't that a problem of the compiler, and not of the lambda idiom in C++ or C++ language itself?
I mean, idk about clang, but at least compiler errors from msvc and gcc have always been hard to understand, with or without lambdas.
While I don't doubt the validity of the argument that it takes longer for a programmer to write correct lambda code in C++ (I have been using them since C++11 was released and still forget the capture list syntax sometime), it's not much more than an academic exercise to take some iterator-based code and replace it with higher-order function calls. It's unfortunate that all the tasks seem to be based around doing that though. I do think it is still reasonable to do this in C++11 once you know how - with -O1 turned on you can get basically the same code spit out using std::transform, std::accumulate, std::for_each, et al, as you would using a for loop.
It's also unfortunate to note that, at least in g++ and clang, there is still significant advantage to using lambdas over std::function and std::bind. Lambdas typically end up being faster in both compile-time and run-time, and end up generating less code. For these reasons I've found myself using them a lot more, especially anywhere I would have done some type of currying. This I do miss somewhat, but it's still leaps and bounds ahead of something I would have written in C++03.
I have to say, std::bind is just a travesty. It's so difficult to bind a member function pointer that takes multiple arguments to an std::function.
I wrote my own so that you can do this with just: function<void (int, int)> f = {&Class::func, &object};
Source is here: http://hastebin.com/raw/kobudabasa
In doing so, it becomes clear there's basically two ways to implement this idea:
1. you allocate heap space to perform type erasure. This results in a pointer indirection plus a virtual function call worth of added overhead. Along with tremendous costs to allocate and destroy the function objects.
2. you store a big chunk of raw memory inside the function<> class, and cast it as necessary to a pointer. This is actually what I did prior to C++11 and lambdas. It was tricky because the size of member function pointers is undefined. Having a vtable makes them larger. So for that I made a complex dummy class to pull its sizeof info.
Option 2 is definitely a good bit faster (at least for constructing/copying/reassigning/destructing them), but you're really butting up against undefined behavior and abusing the language. And capturing lambdas would be even more challenging.
But even with that, yeah, you can't ever really beat concepts that are native to the language like lambdas and virtual functions themselves. Compilers can get really clever and inline things in a way that's exceedingly unlikely to ever occur with std::function, no matter how you implement it.
I only scanned the paper, but I get the impression the title was chosen specifically to grab attention. "An empirical study comparing the use of C++ lambdas versus C++ STL iterators and programmer experience" wouldn't get eyes on their presentation, and eventually, being posted to HN...well, it would, but they'll surely get more reads/downloads this way.
Clickbaiting for conference proceedings, who would have known.
Lambdas really are great.
Nested and member function local classes are actually implicitly friends of the containing class. Not all compilers were conforming in this area though.
Callback<void(int, int)> f = APRINTER_CB_OBJFUNC(&ThisClass::function, this);
One magic thing is that the macro is just a value, it figures out the type itself.
There's zero memory allocation. The Callback class just contains a function pointer and a void pointer. So you can't bind argument values other than the object pointer, but I have no need for that.
I have yet to see a C++ codebase which was good while not written by people who are essentially C++ experts, or through in-depth code reviews by such, for all code. I understand finding the talent may be hard, but then the C++ volume might need to be decreased and replaced by something less demanding, or the code will likely be anything but solid.
As unpleasant as it is, this is all too widespread, because it seems like common-sense on the surface. "A little knowledge (on the part of management) is a dangerous thing."
Interestingly, you can think of lambdas a small functions with single responsibility, which happen to be easy to find (they're inline right there in the code and you don't have to hunt around for them).
On the flip-side, the C programmer who starts out learning C++ will often spit out some hideous abomination that uses no namespaces, consists of obscure function names, pointers everywhere, and tons of callbacks.
Thing is, sometimes performance really matters. A large part of the HN audience focuses only on web-oriented programming. But in scientific computing, finance, etc. being able to squeeze out a few more operations/CPU cycle can be incredibly important.
I think rather than being dogmatic about the issue, as programmers tend to do, it's important to introduce students to C but be very clear about why you might want to use some of its features vs. relying on the safer C++ variants.
In all my years as developer, even back on Spectrum, MS-DOS and Amiga, I never bothered turning off bounds checking and it seldom mattered for the type of applications I was writing.
The very few times it mattered, I was writing big chunks of Assembly anyway.
Everyone thinks their applications have the same performance requirements as Microsoft, Apple, Google, Facebook, CERN, Wall Street, Sony ... have, but they don't.
It is like the native code version of going web scale on day 1, when everything that one has are a few pages to display.
Pointers should be used very sparingly, and much of the time in code I've seen they could have and should have been avoided. But it seems like we've come to some far extreme other side of that thought to the point where seeing a pointer instills an exaggerated fear of memory stomping, leaks, and a variety of other things. These are important concerns, but, like optimization, ought not make a person afraid of their use. Anybody who needs to do non-trivial programming at the systems level had better get over those fears and focus on learning how best to use the tools they need to, in my view.
Then there's std::string. Super nice in 95% of cases but can cripple an application if you have millions of strings you need to deal with (TONS of dynamic memory allocation).
And then there's std::shared_ptr. Super convenient but potentially a huge impact if you have items with very short durations in hot loops. std::unique_ptr on the other hand has no added overhead. Sure, you can look these things up. But it's not really obvious.
C++ is an incredibly useful language. It's far from the safest, prettiest, etc. But when used properly it's a very powerful tool. But knowing C can help you to better understand when you're not getting something for free and when undefined behavior can rear its ugly head.
Its not that C++ will bloat your program, its that many programmers don't understand the tradeoffs of the primitives they're using, or don't have the experience or vision to see what they become at scale.
What usually happens is that the code gets unreadable/unmaintainable quickly and is still a complete unoptimized mess at the macro level.
Once you realize this you don't need C++ for most application domains. And for the domains where C++ matters you also realize you'd be better off in C with a scripting language on top.
And even though it was one of the languages I enjoyed most using after Turbo Pascal and I even gave C++ classes at the university, I am a firm believer that if Java, VB.NET and C# had been fully AOT compiled from day 1, just like many other alternatives that used to exist, C++ might have not taken off as it did.
The rise of VM languages, with other AOT compiled languages fading away and rise of FOSS written mostly in C, made C and C++ the only languages we could turn to when the performance was lacking, thus arriving at the actual situation.
As for using C with a scripting language, without the safety of strong type checking, real enumerations, support for arrays and strings, no namespaces, no RAII, new/delete instead of malloc()/free() and many of safety improvements from C++ over C, that is not something I will ever advise.
I already did that once with Tcl for two years, no need to repeat it.
Being aware of what is happening and the best tools for the job I suppose! And knowing the STL inside out and its idiosyncrasies and foibles.
Until you realize the purpose of the weird syntax in the former.
Namely: you can write algorithms where an iterator is a drop-in replacement for a simple loop through all items in an array via pointer arithmetic, if you write it to take a start and an end iterator as templates.
Plus said algorithm can take sub-ranges instead of requiring it to loop through all items.
I thought c++ iterators were very strange for many years until I understood this and other rationales.
and they don't: http://en.cppreference.com/w/cpp/container/vector/end - there's an overload returning a const_iterator. You don't need to use 'cend'.
And since insertion and deletion potentially invalidate iterators, 'hasNext()' is just as bad.
Perhaps I could instead claim that "item != market.end()" redundantly specifies the vector, "market", and thus presents an unnecessary opportunity for mistakes? For example:
std::vector<double> x_coords;
std::vector<double> y_coords;
...
for (auto y_iter = y_coords.begin(); y_iter != y_coords.end(); y_iter++) {
for (auto x_iter = x_coords.begin(); x_iter != y_coords.end(); x_iter++) {
...
}
}
Notice the "x_iter != y_coords.end()" bug. I assume this will usually trip an assert if it is encountered in debug mode, but not in release mode. Of course you could just as easily mix up "x_iter.hasNext()" with "y_iter.hasNext()", but the removal of redundancy means one less opportunity for a potential mistake. Right? Hmm, I guess that's really an argument for losing the iterators altogether, which I guess was kind of the point of the study.So then wrt iterator invalidation, I have a question. Consider this contrived scenario:
for (auto y_iter = y_coords.begin(); y_iter != y_coords.end(); y_iter++) {
if (5 == std::distance(y_coords.begin(), y_iter)) {
y_coords.resize(3);
}
}
With conventional implementations of std::vector, the "y_coords.resize(3)" will presumably "invalidate" y_iter. And the "y_iter != y_coords.end()" will result in undefined behavior. But you could imagine "safer" implementations of vector<> that would instead throw an exception (or terminate or whatever). (Or you could actually download one of them at the link I gave.) So the question is, if this "safer" implementation supported "y_iter.hasNext()", would it be better for it to throw an exception (or whatever) in this case, or just return false?The safest option is to compile this code as Rust and have it fail to satisfy the borrow checker. And basic syntax parsing because this is C++ - but ehh, details.
Certain static analysis tools may also catch the issue. Well, if you're using real C++ iterators and not ::hasNext at least.
At this point, I find the former much more readable than the latter.
I have seen the former a thousand times. I have seen the latter once - here, and here alone. Oh sure, I've seen similar patterns - under different names, usually under different languages - but I'm pretty sure this is exactly the first time I've ever seen it named "hasNext".
The worst bit is mixing SC++L terms - "::iterator" - and foreign idioms that do NOT conform to the SC++L concept of what I expect an "::iterator" to do. It is surprise and confusion of the worst kind. Even relatively inexperienced C++ programmers that I know would pause as I did, and ask - "Wait, what the fuck?", and then waste the next few minutes rediscovering what the hell the code is doing (absolutely nothing special that would call for special divergence from the norm.)
As such I must thoroughly disagree on the strength of the communication of intent between the two samples. The former might as well be plain English to me - the latter might as well be pig latin. Oh sure, I can figure it out - but it'll take me a minute. It DID take me a minute.
You want to steal patterns from Java or C#? Then at least name them after the Java or C# concept. Call it enumerator. Embrace the fact that you're using Java or C#'s idioms. Decry C++'s idioms as poor choices by the C++ language if you must. And hell, yes, there are exceptional cases where you can say "let's go with none of the above - the best solution is unconventional and unusual, using the normal idioms of no language."
You have very much failed to convince me that this is one of them.
And while you could perhaps argue that C#'s idioms are superior, I would note that you're a few decades late to the party on that count, and talking to the wrong guy. I wouldn't even disagree, necessarily. But C++'s idioms have a useful inertia at this point, and are frankly not that bad. Rather reasonable, even. Although they do have a learning curve.
> First of all, using something like "market.cend() != const_iter" instead is arguably better practice (imagine you unintentionally omit the "!").
I prefer to have a warning-as-error for the unparenthesized assignment you posit (if only because my coworkers probably didn't use yoda conditionals on the existing codebase). But hell, even yoda conditionals would be better.
> But programmers shouldn't need to consider whether the iterator is const or not when they just want to know if the loop is done.
Realistically, I won't - instead preferring auto, or never naming my iterators at all, as I've done in the std::accumulate example - as begin is overloaded appropriately.
> Also, consider the case where the vector is being modified (items inserted or deleted) inside the loop. It might be problematic either way, but "item != market.end()" is particularly bad in that situation.
Oh, I know what I want in this case! Iterator debugging to catch the problem. The PDF has the source code to ::hasNext:
bool marketBasket::iterator::hasNext() { return owner−>items.size() − index > 0; }
Do you see iterator debugging? I don't. What happens when index is greater than .size() because of a removed element?Based on other code, we know items is a std::vector. And thus that .size() is unsigned. And thus that the result of ...size() - index is unsigned. And thus that the result will underflow to a huge number, and that ::hasNext() will return true, leading to a buffer overflow.
If "item != market.end()" is particularly bad, then "iter.hasNext()" must be particularly downright evil, for it's doing even worse.
Note that any fix applied to ::hasNext could equally be applied to comparing against .end().
Yeah, after further consideration I'm not convinced either.
As per my sibling, write me something as straightforward as a C++ RAII-using dtor in C without "using C in crazy ways". I will not hold my breath. The cost of a very minimal subset of C++ is literally zero, and yet significantly improves the likelihood of your code actually working. You're picking social baggage in either case: either understanding the subset of C++ that your team is using or expecting all of your developers to be perfect where C++ (and other languages--shouts, Rust!) just do it for you, correctly.
Technology is socially interesting in that incompleteness and inexpressivity is so often misread as "elegance" or "minimalism". That a language is "simple" is not a feature when it offloads all of the danger onto the (almost invariably failing to consider critical information at the worst possible time, and I include myself at the forefront of that characterization!) developer. This is why we have tools that compensate for the most common, and most destructive, of our mistakes.
(This post should not be construed as any particular endorsement of C++. C++ is a gong show when used improperly. But at least it's possible for a mere mortal to use it properly.)
You can do essentially all the "C in crazy ways" in C++ as well, and people do. In my opinion and experience, it isn't what the language provides, it is how it is used in practice - and again from my experience (YMMV), C is used sanely and C++ is not.
The idea that people use C "sanely" more often than C++ (or, you know, something actually good--further shouts, Rust!) doesn't pass the smell test. Are you checking the retval of every sprintf? Are you writing goto drops in every method where there's allocated memory, diligently checking every error code, and properly bailing out, every time? If so, you're that one percent. But you're probably not. And that's not a slight--I'm not, either. That's why I am proud to count myself as a member of a tool-using species, because we build (and have built) better tools to compensate for our flaws.
But beyond that, it all went downhill. There was no reason to make C++ the most bloated language ever.
The same is true of Javascript etc. Newbies will not be able to start, now, because they will think about let/var/const the same way as in C++ they have to think about all the possible casts, copy constructor vs casting semantics etc.
Tell me this for example, does the following use a cast or a copy constructor?
http://stackoverflow.com/questions/11222076/why-is-copy-cons...
I seldom meet C developers that are able to distinguish between ANSI C and my compiler's C, that extrapolate from my compiler's C version Y, how the language should behave.
Then they port the code to my compiler's C version Y + 1, or another compiler vendor, their code gets broken, blame the compiler, only to find out that the code was already broken from ANSI C point of view.
Is signed arithmetic using C in crazy ways? Is shifting by an arbitrary value without checking to make sure that the number of bits is in the valid range of the type using C in crazy ways?
Undefined behavior is everywhere in C.
Because you know, it's all super clear.
But the language jumped the shark starting with C++0x
> If so, you're that one percent.
I probably am that one percent (and I don't use sprintf, because there is no sane way to use it). When I say people use C "sanely", I do not mean that they never err in any way, far from it. And my observation (your mileage obviously varies) is only that C code bases that I tend to use and meet (e.g. x264, ffmpeg/libav, the linux kernel, the Nim compiler) tend to be saner than C++ code bases that I tend to use and meet (e.g. libzmq, although that one improved dramatically since 4.0, and is now almost sane, boost, stl)
I admit that I have not yet met a C++11 codebase with lambdas - that might have restored sanity. But even if it does, it does not retroactively bestow that goodness on the millions of lines of code already out there.
I stress again - I am not passing judgement on the language, but about how it is used in practice, through my own sample set. If I work on a project in which I can dictate the exact subset, choose the people, etc, I might pick C++. But in most projects I'm involved in, the constraints are dictated in some way or the other that makes C at least as good a choice (and often better) than C++
Except, that I have seen C++11 codebases that heavily relied on lambdas. And that was far from sane. I am very familiar with lambdas from pure functional languages and partially ones (python, ...). But all the syntax specifics, brackets, ... in C++ made it a very annoying process to understand, what was even going on at all.
Using short functions would be more lines of code. But at least I would have known right away what's happening in the code.
Hence why I eventually did a Turbo Pascal -> C++ transition, with a very very short stop in C.
I was lucky that our technical school also had access to Turbo C++ 1.0 for MS-DOS on their software library. As I was not getting why should I downgrade myself from Turbo Pascal 6.0 into C.
Already in those days of "The C++ Report" and when "The C/C++ Users Journal" was still called "The C Users Journal", there were articles how to put C++ features to good use for increased safety.
And this is a major culture gap between C and C++, that I have observed since then, yes we also get the performance obsessed ones, but there are also the security minded ones.
I seldom see talks about how to increase type safety in C, their C99 security annex is a joke as it keeps pointers and size separated, and is so highly regarded that it was moved into optional in C11.
C++ community on the other hard improves the standard library to decrease the unsafe use cases, promotes guidelines and is trying to reduce the amount of UB use cases.
#define NEW(t, args...) ((t*)malloc(sizeof(t)) && t ## _construct(args))
#define DELETE(t) (t ## _destruct() && !free(t) && (t = NULL))