Learning C++11

Started by
44 comments, last by taby 7 years, 4 months ago

I agree learning when to use it should be the end goal, but I can also imagine that you park it for some time, until you have a firmer grasp of the other new things.

That's absolutely a good point. You can't learn everything at once, but have to take things in bite-sized chunks.

Advertisement

When is using a Lambda a good idea?

I also bought Effective Modern C++ by Scott Meyers, which has something to say about Lambdas, but I have not studied it in detail (just got it today from amazon.ca).

There was an example of Lambdas earlier in this topic. I could have instead written a function with a static variable called count.


#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

int main() {
  std::vector<std::string> zoo;
  zoo.push_back("gazelle");
  zoo.push_back("dingo");
  zoo.push_back("eagle");
  zoo.push_back("bat");
  zoo.push_back("ferret");
  zoo.push_back("cougar");
  zoo.push_back("aardvark");

  std::sort(zoo.begin(), zoo.end(), [](auto& a, auto& b) { return a[0] < b[0]; });

  for(auto& animal : zoo) { std::cout << animal << "\n"; }

  std::cin.get();
}
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.

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

int main() {
  std::vector<std::string> zoo;
  zoo.push_back("gazelle");
  zoo.push_back("dingo");
  zoo.push_back("eagle");
  zoo.push_back("bat");
  zoo.push_back("ferret");
  zoo.push_back("cougar");
  zoo.push_back("aardvark");

  std::sort(zoo.begin(), zoo.end(), [](auto& a, auto& b) { return a[0] < b[0]; });

  for(auto& animal : zoo) { std::cout << animal << "\n"; }

  std::cin.get();
}

That is one beautiful piece of code, partially due to the use of auto to avoid having to specify a particular type. Nice! Now that's a gem... a C++14 gem. :)

When is using a Lambda a good idea?

Lambdas are good when you have a tiny block of code that needs to be used as a parameter to something.

A common example is the comparison function to the enormous number of functions that take a comparison function. Various sorting algorithms require a sorting predicate, a function that tells how two values sort relative to each other. Some of these were so common they were built into C, such as strcmp(). Finding the min or max, sorting the various ordered containers, merging, finding ranges in sorted containers, all of these are great for a lambda:


 myContainer.sort( [](const Thing&a, const Thing&b) -> bool  {
  return a.whatever > b.whatever;  
});

They are also great for other types of predicate functions. Searches are one of many of these uses:


find(iter.begin(), iter.end(), [](const thing&a) -> bool { return a.matches_search == true;});

Since these are templates the compiler cam optimize the entire block of code, including the condition, into something much nicer than the original.

Lambdas were originally fancy hidden function object objects, so they required a tiny bit of space and an anonymous type name. They were still functions, so they were a block of code that was compiled somewhere and had an address to a function with a return value.

You could get much -- but not all -- of the same functionality by writing a small function with static linkage. For example, you might have a small function:


// Basically the same as the example above, but not quite because this has a name and cannot as easily be inlined

bool ThingCompare(const Thing&a, const Thing&b) {
return a.whatever > b.whatever; 
};

Or if you are passing more complex parameters, a function object:


// Basically equivalent to the lambda: [x](int n) { return a < x;}

class Foo {
  public: 
    Foo( int x ) : x(param);
    bool operator()(const Thing&a) {
       return a < x;
    };
  private:
  int x;
}

There are additional fun options of how parameters are passed with [&](){} and [=](){} style lambdas.

I write that they are basically equivalent, but they are not exactly equivalent.

When they were incorporated to the language formally that restriction was removed since several compiler vendors had already implemented several optimizations on it. As mentioned above, these little blocks of code have an anonymous and unnamable type, they are simply a block of code and not a function or a functor. They may not have a name, and if the compiler can get rid of them they may not even have an address. Although template types could handle them just fine as an unspecified typename, and existing functionality that required function pointers just fine, there were some edge cases that could not handle it. The auto keyword was finally included to handle this strange little type.

Lambdas are great for parameters of template functions when it comes to inlining and optimizing. Instead of having an address to a function call which may or may not be inlined, the compiler has the exact block of code, and it can decide when generating the code if it should be implemented as raw code, or if it should be implemented as a function call, or implemented as something else entirely. It can optimize the code along with the template code, potentially eliminating or reducing the processing work.

My general guide is that if the code is up to two lines of code, it is good as a lambda. If the code is over five lines it is too complex and belongs in a function. If they are 3-5 lines, use your own judgement.

Love it.

The 2016 word sort:

std::sort(zoo.begin(), zoo.end(), [](auto& a, auto& b) { return a[0] < b[0]; });

1970s word sort:

qsort( words, numwords, sizeof char*, strcmp);

We have come so far. :-)

That is Khatarr's code, which is a great code.

And you give a C comparison, thanks for that. :) Gotta keep it below 6 lines for the lambdas to kick in.

We have come so far. :-)


Yeah, we have.. that C++ code is much better, more flexible and safer than the C version...

(Also, probably faster as I trust the C++ compiler to more aggressively inline the loop and the lambda than I do the C linker to unmuddle that function pointer mess; and of course it does a different thing to the code you posted as the C++ version is sorting on the first character, the C version would sort on the first N giving a different result)

std::sort(zoo.begin(), zoo.end(), [](auto& a, auto& b) { return a[0] < b[0]; });

Why not:


std::sort(zoo.begin(), zoo.end(), [](auto& a, auto& b) { return a < b; });

? It handles zero-length strings... or am I missing something?

I believe it would be undefined behaviour on a zero length string. If iterator checking were on it would fail the bounds checking assert.

If we really want to get analytical about it I should have used a initializer list for the vector as well:


std::vector<std::string> zoo = {
  "gazelle",
  "dingo",
  "eagle",
  ...

We have come so far. :-)


Swapped one brand of gibberish for another, lol. Can you imagine taking modern C++ back to the 70s? The wtf/min would be pretty high.

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.

This topic is closed to new replies.

Advertisement