"Modern C++" auto and lambda

Started by
33 comments, last by nfries88 8 years, 1 month ago

According to Microsoft, using the auto keyword, as well as lambdas can make "cleaner, tighter and easier to read"(abbreviated of course) code. I am not in any industry, but merely a lowly hobbyist. So can someone explain to me the general reasons why this is whats being pushed by Microsoft and if it is being adopted by others, why I am wrong in my disagreement with these statements?

If you remove the standard c++ library, are lambdas not merely just a "fancy" way to scope a block of code? Are anonymous functions really THAT COOL?

And how is auto easier to read? You are implying and hiding the type unless you explicitly and physically look at what it is received by?

Not starting flame war, rather an educational opportunity for my self and others.

Advertisement

If you remove the standard c++ library, are lambdas not merely just a "fancy" way to scope a block of code? Are anonymous functions really THAT COOL?


You're implying that only the standard library uses functors. That's far from the case. You can write your own generic algorithms. ;)
You're also apparently forgetting that C++ lambdas can capture (becoming closures), which means that you can create things like event handlers without the verbiage of creating a class to represent that concept.

And how is auto easier to read? You are implying and hiding the type unless you explicitly and physically look at what it is received by?


A lot of the time we don't actually care what the exact type is so long as the compiler enforces it. auto is a way to cut down on the general verboseness of heavily templated code, among other things. Which of these is clearer?

[source]
for (container_type<foo<int>, bar<char*>>::iterator itor = std::begin(collection); itor != std::end(collection); ++itor) {}

for (auto itor = std::begin(collection); itor != std::end(collection); ++itor) {}
[/source]

If you remove the standard c++ library

why would you do that?


Are anonymous functions really THAT COOL?

Yes. The same thing can be achieved with functors, but lambdas are an order of magnitude less verbose.


And how is auto easier to read?

Templates.


std::vector<std::string, SomeCustomAllocator> strings;

// which is easier to read
for (std::vector<std::string, SomeCustomAllocator>::iterator itr = strings.begin(); // etc

// or
for (auto itr = strings.begin(); // etc

Sure you can solve this problem with typedefs, but does


for (StringVecIterator itr = strings.begin(); //

really add any useful information to the code as you read it?

edit: damn... ninja'd! ph34r.png

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

Sure you can solve this problem with typedefs, but does

for (StringVecIterator itr = strings.begin(); //

really add any useful information to the code as you read it?

Especially since some IDEs will tell you the deduced type if you just mouse over the variable.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

According to Microsoft, using the auto keyword, as well as lambdas can make "cleaner, tighter and easier to read"(abbreviated of course) code. I am not in any industry, but merely a lowly hobbyist. So can someone explain to me the general reasons why this is whats being pushed by Microsoft and if it is being adopted by others, why I am wrong in my disagreement with these statements?

If you remove the standard c++ library, are lambdas not merely just a "fancy" way to scope a block of code? Are anonymous functions really THAT COOL?

And how is auto easier to read? You are implying and hiding the type unless you explicitly and physically look at what it is received by?

Not starting flame war, rather an educational opportunity for my self and others.

On the large Microsoft really wants to empower a large number of software engineers through higher level technology. One might view this whole idea as a poor one when considering this is sort of the purpose of the old .NET stuff. Come ten years later we have two major ways to write code that MS enjoys: C# and the newer more abstraction heavy "modern" C++. This might just be a newer generation of engineers not learning from old mistakes, or it might be that the new high level facilities really can empower tons of engineers to do be more productive.

However there's another argument against the whole "empower the many" option: empower just a few. More experience older game industry vets usually heavily align with the idea that it's more important to have an extremely strong/experienced developer as opposed to a work force of C# coders. This alignment usually revolves around ideas like abstractions have an inherent cost, writing good code is ridiculously hard, adding more people is expensive to facilitate communication, etc.

My personal opinion on auto is that it's only useful if you're using templates. So now the question might be, is it worth it to use templates? Immediately I can see this question starts to get pretty involved, and would probably require some specific examples of real work to justify one way or another. So as long as someone tried something out and shipped a product with it I would care about their opinion. Microsoft certainly ships a lot of products, but I don't really enjoy many of them, so me specifically would not really agree with their process (like auto/lambda).

@ExErvus:

I totally agree with you.

The keyword auto introduced by C++11 is one of the new most overused features.

Auto looks awesome to get rid of typing 8 extra letters, but it's very horrible when you have to analyze code written by someone else and you can't quickly see what the variable's type is, or you don't have an IDE to help you.

Being easy to write != being easy to read & debug 6 months later.

Auto is useful inside templates, since the variable type may not be known until compilation or specialization, or when the syntax inside the template gets too complex. However using auto outside templates (or everywhere!) is an abuse. It can even introduce subtle bugs: e.g.


auto x = 7.0; //This is a double
auto x = 7.0f; //This is a float
auto x = 7.; //This is a double!!!
auto x = 7; //Is this an int or a uint?

The first one may be intended to be a float, but the 'f' was missing.

The third one may be supposed to be an integer, but the extra '.' made it a double. Easy to miss.

The fourth one is the worst kind. Signed integer overflow is undefined behavior. Unsigned integer overflow is well defined (wraps around).

This can have unforeseen consequences:


auto x = myInteger; //int myInteger = 262144; causes undefined behavior as result should be 68719476736, but doesn't fit in 32-bit
if( x * x < x )
{
//The compiler may optimize this away. Which wouldn't happen if 'x' were unsigned.
}

Of course you wouldn't use auto on a literal (note: Scott Meyers actually recommends using auto on literals!!!), but even then, using it on more complex code is asking for subtle trouble.

If the code is too complex, then it is even harder to read what's going on.

Another subtle problem is one like the following:


foreach( auto x : m_foos )
{
}

Looks ok, right? Well turns out m_foos is std::vector<Foo> m_foos; The loop will perform a hard copy Foo in every iteration. If the declaration of m_foos is std::vector<std::shared_ptr<Foo>> m_foos; then every loop iteration will take the lock, increasing and almost immediately after decreasing its internal reference count. The proper fix would be:


foreach( auto &x : m_foos )
{
}

And now x is a reference, rather than a hardcopy.

Auto has far too many issues and problems, even if you're aware of all of them. It's best to just forbid it.

On things like templates it makes sense, because auto myVariable is far better to work with than typename<T>::iterator::something<B, C>::type_value myVariable where the type is not actually explicit, and it's an incomprehensible string. In those cases, auto is justified as the alternative is worse on every single aspect.

So, in my opinion, Microsoft recommending to use auto everywhere like a mantra only perpetuates bad practices.

Randy Gaul, I have no clue why you spent that whole post in the context of Microsoft. The features talked about have nothing to do with them (except they, like quiet a few other people, have a C++ compiler). They are C++11 features. Pretty old ones by now, we already have C++14 and C++17 around the corner. Just because the OP stumbled on them in something written by Microsoft does not mean it's reasonable to look at all the angles Microsoft might have with them. You could as well ask what Microsoft wants with for-loops because they mentioned them in an introductory article about C++ somewhere.

As a matter of fact Microsoft's own compilers have been hanging behind on C++11 features for a long time when others like gcc or clang already had complete or near-complete implementations. I hear they have recently managed to catch up but I could not yet try out the newer compilers in a realistic setting. An evil person might even say that Microsoft only talks about those features now-ish because only very recently they have compilers implementing them properly (although that would be a bit unfair because even though a lot of things were broken/incomplete for a long time, both lambdas and auto worked decently even back in 2012).

Now, discarding that wholly unnecessary Microsoft talk: both features are pretty much core for me. Sure, lambdas could be implemented using a simple class functor. But for small stuff they are much less verbose, for slightly more complicated stuff (needing to capture things) they completely eliminate unnecessary and error-prone boilerplate.

auto is mostly useful in the context of templates (and if you do not use them I seriously question why you even bother with C++) but they are something useful/less verbose in other situations (quickest example: storing a lambda).

Edit: and I have never seen someone do everything with autos the way Matias Goldberg exaggerates, nor seriously suggest that would be a good idea. I'd want some actual quotes on that.

Anecdotally, I once worked in a role at Microsoft where my job was to run major Xbox 360 titles through static analysis tools -- lambda's had just become a thing and one of those titles was an early adopter. Unfortunately the static analysis tool worked against a previous version of the compiler and didn't support lambdas, so to get the job done I had to 'backport' all those lambdas -- around 500 as I recall.

Now, this is a pretty mechanical translation, once you know what the compiler does under the hood its just a matter of following the recipe. Still, a very short lambda (which are overwhelmingly commonplace) translated to a function object goes from 1 line at the exact place you need it, to a 10+ line function object often located in odd places, and sometimes requiring intermediate variables created (depending upon what it captures and how, and where those things are defined and last touched). I gained a certain appreciation of what lambdas actually do for you.

As a result of that, it makes the standard library and similar interfaces much more practical -- "its easier to just write my own quick loop" is no longer an excuse, which encourages better-debugged, more-robust code to be used.

Also, for what its worth, its far from just Microsoft pushing these things. I haven't the foggiest where you got that impression.

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

auto and lambdas can be abused and overused just like many programming features, and when something is shiny and new, people are more likely to abuse them.

But don't let their abuses hide the fact that they are actually very useful.

They are especially handy for one-liner predicates to pass to algorithms. In such situations, either the one-liner is something you want to repeatedly use, and should be separated and "standardized" in your codebase (like std::less, for example, or my own case_insensitive_hash), or, if just used in a single place, shouldn't be seperated from the code that actually uses it.

Here's a real example from my code:


//Frees all the images whose shared-count has reached zero.
//Call this after loading an area, to free all the images that are no longer needed.
void AnimationRetriever::FreeAllUnused()
{
    //Remove all the shared_ptrs that are shared by one or less sharers.
    //Since the map's shared ptrs themselves count as a sharer, they are the 'one' sharecount.
    //We keep the shared pointers in the map for precisely this reason - we want to leave an
    //additional sharer keeping the object alive incase the next loaded area wants to use it.
    MapRemoveValues(this->loadedAnimations, [](const AnimationSPtr &sPtr){ return (sPtr.use_count() <= 1); });
}

It wouldn't make sense to separate this lambda into its own function.

Now if I actually needed this behavior alot, I would make it its own function and pass in a parameter and a member func to call

(like if_mem_func_returns(&(std::shared_ptr::use_count), std::less_equal, 1) ), but since that's actually harder to read, I'd only do that if I found myself using this behavior alot.

Lambdas are also useful to clean up repetitive code from functions.

Here's a slightly messy real example:

(for context, the enclosing function is queuing for rendering the dotted rectangle around selections, with the squares on the corners and sides for stretching and resizing the selection)


    //================================================
    //Draw the grabpoints:

    auto drawGrabPoint = [&](const cPointF &center, bool colorationButton)
    {
        using namespace Engine::Graphics;

        cRectF positionRect = cRectF::CreateFromCenter(center, CornerGrabpointSize);
        positionRect.Round();

        const cRectF &OrangeSubrect = (colorationButton? CornerGrabpointSubRect_Orange_Coloration : CornerGrabpointSubRect_Orange);
        const cRectF &BlueSubrect   = (colorationButton? CornerGrabpointSubRect_Blue_Coloration : CornerGrabpointSubRect_Blue);

        const cRectF &texCoords = (positionRect.Padded(1).Contains(this->lastMousePosInWorld) ? BlueSubrect //Hovered.
                                  : this->stretching ? CornerGrabpointSubRect_Red //Skewing.
                                  : OrangeSubrect); //Normal.

        SetBasicTextureData(quadBatch, quadBatch.GetNextIndex(), positionRect, 0.0f, texCoords, this->guiTexture.GetDetails().textureSize, cColor::Full);
    };

    //Corners:
    drawGrabPoint(decal.corners.topLeft,     false);
    drawGrabPoint(decal.corners.topRight,    false);
    drawGrabPoint(decal.corners.bottomRight, false);
    drawGrabPoint(decal.corners.bottomLeft,  false);

    //Sides:
    drawGrabPoint(cPointF::PointBetween(decal.corners.topLeft,     decal.corners.topRight),    false);
    drawGrabPoint(cPointF::PointBetween(decal.corners.topRight,    decal.corners.bottomRight), false);
    drawGrabPoint(cPointF::PointBetween(decal.corners.bottomRight, decal.corners.bottomLeft),  false);
    drawGrabPoint(cPointF::PointBetween(decal.corners.bottomLeft,  decal.corners.topLeft),     false);

    //================================================

(note that any mess is from my code within the lambda, not the usage of the lambda itself. The lambda makes the code clearer)

If the lambda was larger, I'd separate it into a standalone function called something like priv_drawGrabPoint(...), which would also have to take several additional parameters that the lambda was capturing for me.

Previously, I had to make every example of this time be a stand-alone "implementation" function, even when it was just a few lines of code, which unnecessarily separates related code from being right next to each other.


foreach( auto x : m_foos ) {}
Looks ok, right? Well turns out m_foos is std::vector<Foo> m_foos; The loop will perform a hard copy Foo in every iteration. [...snip...]

foreach( auto &x : m_foos ) { }
And now x is a reference, rather than a hardcopy.


But that's not an auto issue. That's a misuse of variables in-general issue.
If you did:


for( Foo x : m_foos )

...the exact same flaw would occur.

Further, I would suggest by default one should use const-reference, and only go to non-const reference or value (or r-value) is the code needs it (for example, if it's just integers).
If you use good coding practices, then "auto x : m_foos" stands out just as badly as "Foo x : m_foos".

I made me whole ui using lambdas and I realised just how amazing they are. I also use them for a event/observer-listener type system. It is so much faster, neater and can all be in one place.

If my device rotates I need to change a layout accordingly.I can pretty much listen for an event in a single line:

device.RegisterForEventConfigChanged(this, [this](float width, float height) { ChangeLayout(width, height); });

The first 'this' is used as a reference so I can unregistered later easily enough. Doing that without lambdas would be so much more involved. As has been pointed out in this thread already their power comes in their ability to capture variables, in my case capturing 'this' and being able to call ChangeLayout (which is a class member method).

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

This topic is closed to new replies.

Advertisement