Lambda expressions in C++ (and in general..)

Go To Last Post
45 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

In the thread What makes C++ hard the discussion went into C++ lambda expressions on its second page. I was on my way to post one of my usual long posts there, but decided to not "derail" and am starting a new thread.

 

This is

1) to help those not familiar with the concept getting a first grip on it, and

2) countering any argument that lambdas have no practical use in C++, and also

3) a short look outside of C++ re lambdas

 

Below is the post I wrote:


 

For everyone here, let me elaborate on lambdas in C++. The example will be stupidly trivial, but I'll hope you'll see beyond that.

 

As I said above, you can think of lambdas as "in situ" anonymous functions.

 

Let's start with an example without lambdas. Here is a complete program that uses function pointers and explicitly written functions.

 

#include <cstdlib>
#include <stdio.h>
using namespace std;

const int arraySize = 5;

// A function that applies an "operations" to every element in an array.
// The operation is in the form of a function pointer that takes a reference

void applyToElements(int array[], void (* f) (int&)) {
    for (int i = 0; i < arraySize; i++){
       f(array[i]);
    }
}

// An operation that increments an int vaariable
void addOne( int & i) {
    i++;
}

// An operation that decrements an int variable
void subOne( int & i) {
    i--;
}

// Print the elements of an array
void printElements(int array[]) {
    for (int i = 0; i < arraySize; i++){
        printf("%i ", array[i]);
    }
    printf("\n");
}

int main(int argc, char** argv) {
    // Set up an array, and print its content
    int array[arraySize] = {1, 2, 3, 4, 5};
    printElements(array);

    // Apply the addOne operation, and show the result
    applyToElements(array, addOne);
    printElements(array);

    // Apply the subOne operation, and show the result
    applyToElements(array, subOne);
    printElements(array);

    return 0;
}

Again, this is trivial. In real life, the operations would likely be more complex.

 

Now, I only actually use the addOne() function in one place. For such situations, wouldn't it be nice if we could instead write that code directly in the call to applyToElements()? This is what lambdas allows you to do.

 

The simplest syntax for a lambda is

[] (parameter-list) { body }

where parameter-list and  body are the same as for a normal function. The square brackets are at the start of a lambda, and can contain a so called capture list. I won't go into that now, since we dont need any capture list for the example. But the square brackets are always needed - they serve to signal the start of a lambda, even if the capture list is empty.

 

So, taking our addOne() function from above and inserting its parameter list and body into the lambda syntax we get

[](int & i){i++;}

and there is your anonymous function! It satisfies the type definition for the function pointer we have as the second parameter to the applyToElements() so we can pass this lambda expression as an actual parameter! Here's the same program as above, but utilizing lambda expressions:

#include <cstdlib>
#include <stdio.h>
using namespace std;

const int arraySize = 5;

void applyToElements(int array[], void (* f) (int&)) {
    for (int i = 0; i < arraySize; i++){
       f(array[i]);
    }
}

void printElements(int array[]) {
    for (int i = 0; i < arraySize; i++){
        printf("%i ", array[i]);
    }
    printf("\n");
}
int main(int argc, char** argv) {
    int array[arraySize] = {1, 2, 3, 4, 5};

    applyToElements(array, [](int & i){i++;});
    printElements(array);
    applyToElements(array, [](int & i){i--;});
    printElements(array);

    return 0;
}

Notice how I do not need to write out a complete explicit and named function definition when I only need the operation at one place in the code. 

 


 

So, that's a trivial example of no practical use. Where do lambdas actually come into use, then? One example is when sorting complex data types. Lets say you have a struct with data only (for the sake of simplicity I'm keeping away from classes and OO encapsulation here). The struct holds data about a person. You then have an array of such structs. From here on, code is sketchy and untested!

struct Person {
    char name[50];
    char street[50];
    int zip;
    char city[25];
    int age;
};

Person persons[] = { ... };

 

You now want to sort this array in different ways. For this we have a generic sorting function. I won't show the detailed code for that, but every sorting algorithm needs to repeatedly compare two elements from the data it is sortiing. To make our sorting function generic we isolate this comparison in separate functions, e.g. like this (sorting/comparing on age and name):

int compareAgeName(Person p1, Person p2) {
   // First compare on age
   int retVal = p1.age - p2.age;

   // If ages where equal, then compare on name
   if (retVal == 0) {
      retval = strcmp(p1, p2);
   }

   return retVal;
}

Our sorting function looks like this:

void sortArray(Person persons[], int (*compare)(Person, Person)) {
    // Some simple "double-loop sorting (very sketchy!)
    for (int i = 0;  ...) {
        for (int j = i + 1; ...) {
            // Somewhere in here we need to compare two elements to see
            // if they are to be re-arranged
            if (compare(persons[i], Persons[j])) {
                // Re-arrange!
                ...
            }
        }
    }
}

Now, actually doing the sorting becomes

sort(persons, compareAgeName);

At another place we  want to sort on age and zip, so we'll write another comparison function:

int compareAgeZip(Person p1, Person p2) {
   // First compare on age
   int retVal = p1.age - p2.age;

   // If ages where equal, then compare on zip
   if (retVal == 0) {
      retval = p1.zip - p2.zip;
   }

   return retVal;
}

and the actual sorting will be

sort(persons, compareAgeZip);

If the different sortings are only needed in one place, lambdas will allow us to pass the relatively simple code for the comparison directly at the call to sort(), and there will be no need to write explicit and named comparison functions. The syntax for the lambda gets a wee bit more complicated since we need to state a return type also:

 

[] (parameter-list) -> return-type { body }

The second sorting, utilizing lambdas, becomes:

sort (persons, [] (Person p1, Person p2) -> int {
      // First compare on age
      int retVal = p1.age - p2.age;

      // If ages where equal, then compare on name
      if (retVal == 0) {
         retval = strcmp(p1, p2);
      }

      return retVal;
   }
);

Yes, all of those lines are the actual parameter list to sort()! Newcomers to lambdas are often flabbergasted. And overdoing it will make for a simple call being so many lines of code that it becomes unreadable). 

 

BTW, I actually use a somewhat different indentation style for things like the above to make it clearer (IMO):

 

sort (
   persons,
   [] (Person p1, Person p2) -> int {   // <-- Start of lambda is on its own line - no "strange double-indents"
      // First compare on age
      int retVal = p1.age - p2.age;

      // If ages where equal, then compare on name
      if (retVal == 0) {
         retval = strcmp(p1, p2);
      }

      return retVal;
   }  // <-- This bracket now is in the same column as the start of the lambda on the third line.
)

 


 

Additional material:

https://en.wikipedia.org/wiki/An...

http://en.cppreference.com/w/cpp...

https://msdn.microsoft.com/en-us...

 

https://blog.smartbear.com/c-plu...

 


Aside and IMO: For anyone into "general programming" studying lambda expressions is a must. Lambda expressions has been, or are being, introduced in most general programming languages, e.g. in

 

  • C# around year 2007
  • C++ with the C++11 standard
  • Java with the Java 8 standard, 2014

 

In both C# and Java they are used to make it possible to write code that is more explicit about the intent the programmer has, and hide technical intricacies. LINQ in C# and streams in Java are excellent examples of lambda expressions in good use. In some programming languages, and in general and/or theoretical literature you will see the term "closure" (which the theory people might argue is somewhat different from a lambda expression...).

 

It is somewhat surprising that Java got onto the lambda wagon so late. Especially when, in hindsight, you see how it has been used to introduce new library functionality that enables one to code much more on "the intention level". 

 

Once you grasp the basic concept of lambdas/closures you have a foundation for taking on lambda expressions in any language. It's not only that "resistance is futile". It's actually about a general programming concept that will serve you well in some circumstances.

 


EDIT: Corrections, in green.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Tue. Jul 18, 2017 - 12:42 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Thank you Johan -

 

Another of your incredibly lucid and helpful posts. I've never knowingly dealt with Lambda expressions and have only encountered them, sketchily, in discussions such as the other thread. 

 

Really appreciate the effort you have put into this!

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Jim!

 

I recommend not digging into lambdas any deeper until the basic OO stuff is secured.

 

By all means, read the above to get a first glimpse. That was why I wrote it. But don't dig deeper or start meddling with it.

 

(My secondary goal was to try to lure the ppl wanting to discuss goods and bads of lambdas over here, and away from the "Whats difficult..." thread - so that that thread does not run away in all directions. Let's see if they bite.. (-: )

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I am finding these threads quite fascinating and possibly even somewhat educational.

 

Iterating object dicts is a feature I love in the Postscript language.  Curiously I find some of the way the C++ code is formatted hard to read. Namely the use of the Pointer address modifier &.  It also bothers me when people put a space after the * when de referencing a pointer. 

 

If I see (int & i) that to me parses as a bitwise and.  where as (int &i) reads as a passed argument.    int has no real meaning in c as it is simply an abstraction defined by the hardware.  I suspect that no one would really use int int;  Not sure if that is legal in C.  Such things are legal in postscript as one is a type an the other is a name.  I suspect this is what makes object oriented programming hard to read by anyone other than the programmer, or who can ask questions to the programmer.   There is just too much ambiguity when one can arbitrarily redefine things.

 

C++ reminds me of the conundrum that  True can only be true, however if False can pretend to be true, then if a falsehood that claims to be true really the truth?  I think this puzzle can also be done with a frog and three doors, however the frog always lies. 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Heap good fun, Mr. Ekdahl.  An excellent introduction.  One minor kvetch, though...

 

 

JohanEkdahl wrote:

int zip;

 

Unless that's a 32-bit int, the USA will not like you.  Downtown Los Angeles is 90007, bits of south Denver are 80210, &c.  :)

Might work fine in Sweden.

 

S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to remind folks that lambdas are a fairly recent addition to C++ so I think to use in avr-g++ you are going to need something like -std=c++11 or similar.

 

(actually this just proves the point that started that other thread - here is yet another new syntax I have to now try and carry around in my brain - it seems almost endless!)

 

((oh and I first came across lambdas in Python where it seems to me the syntax is "more obvious" ;-))

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Just to remind folks that lambdas are a fairly recent addition to C++ so I think to use in avr-g++ you are going to need something like -std=c++11 or similar.

 

The usual joke comment applies: "Gee, I wish I've said.."

JohanEkdahl wrote:
C++ with the C++11 standard

 

;-)

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I searched this thread and the first mention I can find of "-std=c++11" I can find is actually in my post. I was telling people how to do it in avr-g++. I'm not sure everyone reading this is going to know the syntax of the -std= option.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm doing some experiments on my PC with all kinds of C++ features. To this end, I'm grafting C++ stuff in a small Windows command line program I was working on, originally written in plain C. So, for example, I replaced pointers with references, see where it could be done, where it couldn't, etc.

 

So when I got to the lambdas, the compiler warned me about "-std=c++11", and I added it. Immediately I got weird linker errors, that I traced back to the use of the "-flto" optimization flag (after a couple of hours...). If I remove this optimization flag, everything compiles fine.

 

In fact, the errors also go away if I use  "-std=gnu++11", that is, enable gnu C++ extensions. Since I'm not using any extensions in my source code, I suppose the "-flto" flag somehow needs them.

I thought I'd just leave this information here. Now, back to experimenting with lambdas...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
Now, back to experimenting with lambdas...

Have fun! (-:

 

I'd recommend to play around with C++ with your host OS (e.g. Windows) as target, rather than AVR. Much simpler to e.g. dump data to stdout on such a n environment.

 

Several good "free" C++ IDEs exists:

- NetBeans (with e.g. MinGW/MSYS for getting the compiler proper). This is my favourite at the moment. Needs Java.

- Eclipse with the "CDT". Needs Java.

- Microsoft Visual Studio, Community Edition (craves a new(ish) Windows - e.g. with Windows 7 you're out). Needs .Net.

- CodeBlocks.

 

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, I'm using NetBeans with MinGW/MSYS, targeting Windows. I tried CodeBlocks but I like NetBeans better, even though it seems quite a bit slower to parse the source code.

For quick experiments with AVR C++ I use the Arduino IDE.

Last Edited: Sat. Jul 15, 2017 - 05:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now, I only actually use the addOne() function in one place. For such situations, wouldn't it be nice if we could instead write that code directly in the call to applyOperation()? This is what lambdas allows you to do.

As always, following such excellent explanations, i got lost at the very beginning. The first seems to be just a slight misnaming by Johan, when he refers to applyOperation() when he actually meant applyToElements(), if I'm right.

 

Secondly, re the rhetorical question above ("wouldn't it be nice..."), I'd like to know what exactly would block me in just doing so?

 

Thanks a LOT for providing such articles, Johan!

 

-- Thilo

Einstein was right: "Two things are unlimited: the universe and the human stupidity. But i'm not quite sure about the former..."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, I thank for the great explanations for lambda expressions (they are more difficult to read than Python's). For making them easier to read, as if one cannotunderstand them, it might be dangerous to try to adapt software, , I looked at astyle and indent (both exist in the GNU linux world, the cygwin demi monde and are windows ported) -without extra options ; just plain invocation- :

 

astyle's results are easier ro read (and are similar to Johan Ekdahls recommandations)...

cat lambda1.cpp && astyle lambda1.cpp && echo -e "resultat de astyle\n" && cat lambda1.cpp
sort (persons,[](Person p1, Person p2)->int
      {                         // <-- Start of lambda is on its own line - no "strange double-indents"
      // First compare on age
      int retVal = p1.age - p2.age;
      // If ages where equal, then compare on name
      if (retVal == 0)
      {
      retval = strcmp (p1, p2);}

      return retVal;}           // <-- This bracket SHOULD BE now ?is? in the same column as the start of the lambda on the third line.

)
Formaté    /home/briond/lambda1.cpp # invokes astylee
resultat de astyle

sort (persons,[](Person p1, Person p2)->int
{   // <-- Start of lambda is on its own line - no "strange double-indents"
    // First compare on age
    int retVal = p1.age - p2.age;
    // If ages where equal, then compare on name
    if (retVal == 0)
    {
        retval = strcmp (p1, p2);
    }

    return retVal;
}               // <-- This bracket now is in the same column as the start of the lambda on the third line.

     )

 

Last Edited: Tue. Jul 18, 2017 - 08:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can you "return" lambdas, if a function returns a pointer to a function?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DO1THL wrote:
just a slight misnaming by Johan, when he refers to applyOperation() when he actually meant applyToElements(), if I'm right

You're right. Corrected.

 

DO1THL wrote:
Secondly, re the rhetorical question above ("wouldn't it be nice..."), I'd like to know what exactly would block me in just doing so?

I'm not sure, but I think you're missing the point. Due to a mis-reading?

 

I'm not talking about placing the code in the implementation of applyToElements(). The point with applyToElements() is that it only knows how to "traverse" the array and apply some operation to all elements. I tried to illustrate this by having both an addOne() and a subOne() operation. Anything that has the signature

void f*(int);

can be passed to the applyToElements() function.

 

Now, if I only actually use addOne() in a single place in my source  then it's some extra work to actually write the function. Lambdas allows you to write the actual code (for e.g. "add one") at the actual call to the function - rather than passing a function pointer. The lambda effectively creates an anonymous function "in situ" and passes a pointer to that function.

 

 

Not sure if this clears the thing up. Come back with elaborations on your question if not.

 


 

 

Let's make this clear: The example in my OP is bot the only reason and use for lambdas. It is only a demonstration of the concept as such.

 


 

westfw wrote:
Can you "return" lambdas, if a function returns a pointer to a function?

Have you tried? ;-)

 

With some limitations/complications it is entirely possible. If keep the limitation of an empty capture list a lambda expression can implivitly be converted to a function pointer. So (SKETCHY! NOT TESTED! You do the testing..):

 

typedef OpFuncPtr void (*f)(int&);

OpFuncPtr addOneFactory() {
    return [](int & i) {i++;}
}

void applyToElements(int array[], OpFuncPointer) {
   .
   .
}

int main(int argc, char *argv[]) {
    .
    .
    OpFuncPtr myOpFuncPtr = addOneFactory();
    applyToElements(array, myOpFuncPtr);
    .
    .
    // Or, of-course..
    applyToElements(array, addOneFactory());
}

Please understand that the example might not make any sense but for demonstrating the capability.

 

A somewhat more realistic example might be to have a "factory function" that can return different lambdas depending on some parameter to the factory.

 


 

I have been waiting for someone to notice that in the first lambda example in the OP I've missed one obvious thing that could be done. With that teaser/hint - can anyone spot it?

 


 

Sideshow: If there's anyone here coding in Java and still haven't taken Java8 to heart and learned Javas lambdas and the new stream functionality - do so now! Your sorting, searching, modifying and whatnot of e.g. ArrayLists will shrink from perhaps 25 to possibly several hundreds of lines of code to  single (or low two digit) numbers of lines of code. And the code will be freed from minute details and instead will reflect only the "intent", more or less.

 

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Tue. Jul 18, 2017 - 01:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 if (compare(persons[i], Persons[j])) {// C(++) is case sensitive

this a very tiny typo.

What would happen if one wanted to sort one out of (2,3) elements ?-say pers[0], pers[2] ... are sorted, pers[1]...pers[2k+1]... remain in place-

(a function to increment the index (or go twice, 3 ... k times to the next elementà might be useful)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dbrion0606 wrote:
What would happen if one wanted to sort one out of (2,3) elements ?-say pers[0], pers[2] ... are sorted, pers[1]...pers[2k+1]... remain in place

I fail to understand that question completely, but you seem to be discussing the sorting algorithm proper. That does not change in my sorting example.

 

It is only the comparison that the sorting needs that is varied.

 

You seem to want to implement something that is not a "bog standard" sorting algorithm that sorts all elements. Yes, you could write such a one. But the comparisons, now of elements 0, 2, 4 etc, will still be needed - and that is what the lambda expressions are contributing.

 

Oh, if your question was re my "riddle" above, then that's not it.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My question was not re your riddle ,

but sometimes, one wants to sort , say, only people whith a given gender, or living in a given city (say, compute the conditional median of age w/r city : this can make difference between cities where retired people live -South of France- and cities with young children -towns in North of France- , or ...)  (perhaps adding pointers to the next element in the same city would make things easier : i++ being substituded with next element of the list ; termination would be different, too;  but both next_element and termintaion can be functions?).

Edited : advantages would be sorting is "in place", with CPUs having large enough RAM -this topic is "general Programming", goes beyond avr-s , but with less RAM than a PC  (where conditional copies and general purpose sorting is enough- ) .

Last Edited: Tue. Jul 18, 2017 - 04:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dbrion0606 wrote:
sometimes, one wants to sort , say, only people with a given gender, or living in a given city

Well, now you're doing first a selection and after that a sort.

 

Now the sorting that I did does not destroy anything in the list - all elements are still there after the sort, albeit in a different order.

 

The selection, however, either needs to destroy/remove the elements we do not want, or needs to select the elements we want into a new array. This is where my small example will degenerate. We would be much better off using one of the standard collection containers in STL. We could then select into a new container without destroying the original one, and then sort in the new container.

 

This will have little or nothing to do with lambda expressions (depending on the approach) but very much to do with templates, collections in STL, and potentially algorithms in STL, and potentially "predicates". It is beyond the subject of this thread. And quite possibly beyond what you would utilize on an microcontroller (e.g. depending on if it has 1K flash/256 bytes RAM or 128K flash/16K RAM).

 

I will not delve into it here.

 

A 15 second Google gave this which might (or might not ;-) ) hint as to how it looks: https://stackoverflow.com/questi... . (And that StackOverflow thread also illustrates where C++ syntax can become somewhat clumsy, as compared to e.g. Java where streams would offer an elegant solution.)

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Tue. Jul 18, 2017 - 06:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, now you're doing first a selection and after that a sort.

This is the way it is done on a PC (one often induly assumes there is infinite RAM).

having other ways of "indexing" / (accessing next item with the same condition : having even indexes/being of the same gender/city than the 1rst item, say) might be very useful on chips with less RAM than a PC/ more RAM than an AVR... and that sorting would be non destructive (original order would not b e kept, but values would be kept with no extra array) ....

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the test code where I'm doing my lambda experiments, it was necessary to cast the lambda function into a pointer. So I found the limitation that only "captureless" lambda functions can be cast as pointers, but inside the lambda I still needed to access variables from the scope containing the lambda without passing them as arguments.

For example, I don't want to pass the "this" pointer as argument to the lambda.

 

Fortunately I found out that static (and global) variables don't need to be captured, or this would be a severe limitation IMO. Even so, I had to copy "this" to a static pointer so the lambda could access it without capture.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
I had to copy "this" to a static pointer so the lambda could access it without capture.
That doesn't sound right. Suppose you have two object instances of the class where this code is used and the code of one is called in an interrupt context. You only have a single static pointer so one may well mess with the other (ie re-entrancy issue).

 

I don't know anything about lambda's in C++ but I'm pretty sure that such a "singleton" mechanism should not be required to do whatever they do.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You're right, that could happen, however not in this case, this program runs on windows and the function cannot possibly be reentered.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

inside the lambda I still needed to access variables from the scope containing the lambda without passing them as arguments.

For example, I don't want to pass the "this" pointer as argument to the lambda.

 

Fortunately I found out that static (and global) variables don't need to be captured, or this would be a severe limitation IMO. Even so, I had to copy "this" to a static pointer so the lambda could access it without capture.

Careful now!

 

You can refer to statically allocated variables, but do understand that this is not a capture. If you define a lambda that refers to a global, then change the global and then invoke the lambda, it will run using the new value of the global.

 

A capture  works the other way around. The value is "bound" when the lambda is defined.

 


Re handling lambdas with capture lists, e.g. returning them from functions, this is quite possible but not with a classic function pointer. Instead, use the std::function<> template out of the Standard Template Library (STL). Example (as always - sketchy, not tested):

#include <cstdlib>
#include <stdio.h>
#include<functional>     // <-- NEW HEADER!!!

using namespace std;

struct Person {
    char name[50];
    char street[50];
    int zip;
    char city[25];
    
    int age;
};

Person persons[] = {
    {"John Doe", "Generic Street 1", 67890, "Big Apple", 30},
    {"Alan Alanbrooke", "High Street 12", 43251, "Smallton", 42},
    {"Cesar Alanbrooke", "High Street 12", 43251, "Smallton", 7},
    {"Boris Alanbrooke", "High Street 12", 43251, "Smallton", 12},
    {"Alan Alanbrooke", "High Street 12", 43251, "Smallton", 42},
};

std::function<void (Person)> printIfUnderAgeFactory(int ageLimit) {
    return [=] (Person person)
    {
        if (person.age < ageLimit) {
            printf("%s\n", person.name);
        }
    };
}

void applyToElements(Person persons[], int numberOfElements, std::function<void (Person)> f) {
    for (int i = 0; i < numberOfElements; i++) {
        f(persons[i]);
    }
}

int main(int argc, char** argv) {

    int ageLimit = 35;
    std::function<void (Person)> f = printIfUnderAgeFactory(ageLimit);

    printf("\nPersons under  (std::function):\n");
    applyToElements(persons, sizeof(persons)/sizeof(*persons), f);
    
    return 0;
}

Notice how std::function has overloaded the operator () .

 

I chose not to do a typedef

typedef std::function<void (Person)> personFunction;

to keep the example "open". In real code you would probably do that typedef, though.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Templates... didn't get to that part of C++ yet cheeky

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

this is not a capture.

 What's a "capture"?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, westfw. I might have been a bit sloppy with terminology there - mostly because this is semi-new and I'm not rock-steady myself.

 

As said above,  a C++ lambda starts with 

[]

This servers both as a lexical element to introduce the lanbda, and it can contain a list of variables to "capture". The list is called a capture list. The concept is that at the point of definition you values of variables are "captured" and used in the lambda. The variables can have their values changed later on but the lambda uses the values at the time of capture. The variables can even cease to exists - the lambda still has the values from "the point of capture".

 

In my example above I lazily used the notation for "capture all":

[=]

I could have been more specific and captured only the variable that I am actually using, like so:

std::function<void (Person)> printIfKidFactoryWithCapture(int ageLimit) {
    return [ageLimit] (Person person)
    {
        if (person.age < ageLimit) {
            printf("%s\n", person.name);
        }
    };
}

Take a look again at my complete example in #24: I pass a value as a parameter to the "factory" that creates the lambda. In the lambda definition I capture the value of that parameter. The lambda is created and the factory function returns it. And there the parameter to the function ceases to exist! But the lambda still has the value captured, and will use it when it is later invoked.

 

More here, of-course: http://en.cppreference.com/w/cpp... .

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am just wondering whether C++ is capable of expressing the following GNU-C code:

 

#include <stdio.h>

typedef void (*handler_t)(void);

extern void run_handler (handler_t);

// Execute `handler' or do whatever we can do with a void(*)(void) function.
void run_handler (handler_t handler)
{
    handler();
}

static void magic (int);

int main (void)
{
    magic (2);
    return 0;
}

// Code above should be the same in C++
// In particular, a "handler_t" is a void(*)(void) function.
// Code below may be adjusted to whatever C++ which can perform the same magic.

void magic (int c)
{
    void h (void)
    {
        printf ("Value = %d\n", c);
    }
    run_handler (h);
}

magic  cooks up a void(*)(void) function which can access variables of its context like parameter c of the host function.  Even though run_handler just gets a void(*)void function, when running it, the program prints

 

Value = 2

 

Is there something in C++ that can achieve this without changing the interface of run_handler?  ALso notice that c is not in static storage but an auto object that lives somewhere on the "stack".

 

avrfreaks does not support Opera. Profile inactive.

Last Edited: Sun. Jul 23, 2017 - 06:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This kind of situation is exactly why I've been looking at lambda expressions. You can do it in C++, but requires a static variable.

 

void magic (int c)
{
    static int scratch;
    scratch = c;
    auto h = []
    {
        printf ("Value = %d\n", scratch);
    };
    run_handler (h);
}

However, nested functions are not standard C yet (I think?).

 

Edit: this is with my limited knowledge of C++, maybe the method in post #24 would work but it is beyond my grasp of C++.

 

Edit 2: Also, this doesn't work in AVR:

#include <stdint.h>
#include <avr/io.h>

(...)

void magic (uint8_t c)
{
	void h (void)
	{
		PORTD = c;
	}
	run_handler (h);
}

you get: "Error        sorry, unimplemented: nested function trampolines not supported on this target"

You also need an intermediate static in AVR.

Last Edited: Sun. Jul 23, 2017 - 07:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> This kind of situation is exactly why I've been looking at lambda expressions.

> You can do it in C++, but requires a static variable.

 

With c in static storage it is trivial.  My example is not in static storage.  static storage is not reentrant, so it's not a replacement.

> However, nested functions are not standard C yet (I think?).

 

As I wrote, it's GNU-C (1st line of my post, word no. 13 ;-))

 

A capture is not a solution because you'll need a different handler prototype then, it will no more be void(*)(void).  Just image the function which consumes a handler cannot be changed (system library for example).

 

 

 

avrfreaks does not support Opera. Profile inactive.

Last Edited: Sun. Jul 23, 2017 - 07:40 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note my edit above about AVR, and the "sorry, unimplemented: nested function trampolines not supported on this target" error.

So, GNU is using some "trick", the so called "trampolines" to make it work (I don't know how it works, I'm not a GCC developer like you, maybe it requires dynamic code generated in RAM, thus being impossible in AVR).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

[EDIT: Just a note that this post was written when post #24 was the last one in the thread. Took a long time to do this. This post might seem a bit confused or redundant given the posts that wedged in between #24 and this. ]

 

Georg-Johann!

 

Not sure where you're aiming..

 

If we only think in terms of lambdas then this won't work:

void magic (int c)
{
    handler_t h = [] () {
        printf ("Value = %d\n", c);
    };
    run_handler (h);
}

It will fail to build. g++ emits these errors/notes:

main.cpp: In lambda function:
main.cpp:28:33: error: 'c' is not captured
         printf ("Value = %d\n", c);
                                 ^
main.cpp:27:20: note: the lambda has no capture-default
     handler_t h = [] () {
                    ^
main.cpp:25:17: note: 'int c' declared here
 void magic (int c)
                 ^

The vartiable c needs to be "captured" - as discussed in previous posts.

 

But doing that:

void magic (int c)
{
    handler_t h = [c] () {
        printf ("Value = %d\n", c);        
    };
    run_handler (h);
}

will make the build fail since the lambda is no longer type compatible with your handler_t:

main.cpp: In function 'void magic(int)':
main.cpp:30:5: error: cannot convert 'magic(int)::<lambda()>' to 'handler_t {aka void (*)()}' in initialization
     };
     ^

Since the rules of the game, stipulated by you, does not allow us to change this type we can not use a lambda.

 

As far as I know C++ does not allow nested functions, so that's out.

 My last, desperate, try was with an inner class (inner to the magic() function) with the () operator overloaded. Something like (very sketchy!):

void magic (int c)
{
    struct H{
        int my_c;
        H(int c) : my_c(c) {};

        void operator() () const{
            printf ("Value = %d\n", my_c);
        }
    } h(c);

    run_handler (h);
}

but that H will of-course not be type-compatible with your handler_t.

 

With your rules of the game I can not see how it can be done.

 

Unless of-course, we may do the really dirty trick:

 

int global_c;

void h (void)
{
    printf ("Value = %d\n", global_c);
}

void magic (int c)
{
    global_c = c;
    run_handler (h);
}

But I have a feeling this is not really acceptable by you.. And excuse me for a moment, I need to go and be sick for a while. ;-)

 

So, no: I can not see how it can be done. I will gladly be proven wrong, though!

 

All tests where done with g++ version 7.1.0 from/in a MinGW build (i686-7.1.0-posix-dwarf-rt_v5-rev0), using C++ standard C++11.

 

Finally: Thank you for an interesting challenge. I learned some things (or refreshed forgotten knowledge). Fun!

 

I am attaching my complete C++ file with all the attempts if that can be of use for anyone wanting to carry on experimenting.

Attachment(s): 

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Sun. Jul 23, 2017 - 08:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> Since the rules of the game, stipulated by you, does not allow us to change this type we can not use a lambda.

 

Thanke for confirming.

 

I should have added "reentrant" to the game, so static variables are also out.  It's just fun to see how "modern" C++ cannot even model really old C extensions (which were not added just for fun but because they have real use cases).  FYI, the GNU-C code doesn't compile for AVR because it doesn't support trampolines.  Likely it can be implemented without executable stack, but I never saw an avr-guy needing trampolines.
 

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
This kind of situation is exactly why I've been looking at lambda expressions. You can do it in C++, but requires a static variable.

A static variable is required only as long as there is a requirement to inter-operate with C. If we are allowed to re-write run_handler() in C++ (and use a std::function<> instead of handler_t) then we can use a lambda with capture:

 

#include <stdio.h>
#include <functional>

typedef std::function<void (void)> Handler;

// Execute `handler' or do whatever we can do with a Handler.
void run_handler (Handler handler)
{
    handler();
}

static void magic (int);

int main (void)
{
    magic (2);
    return 0;
}

void magic (int c)
{
    Handler h = [c] () {
        printf ("Value = %d\n", c);
    };
    run_handler (h);
}

And yes, with this I have of-course thrown the C interoperability with C (which I believe was one of  Georg-Johanns requirements) overboard.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Sun. Jul 23, 2017 - 08:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
It's just fun to see how "modern" C++ cannot even model really old C extensions (which were not added just for fun but because they have real use cases)

C++ can "model" them quite good. It's when you want to have interoperability with C that it fails.

 

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Sun. Jul 23, 2017 - 08:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And of-course: Don't blame C++, the language. Blame the g++ folks.

 

Facts:

Nested functions are not in The C standard. It is a GCC extension.

Nested functions are not in the C++ standard. The g++ folks has not implemented nested functions as an extension.

 

Speculation:

The g++ folks, I speculate, have opted to not implement nested functions as an active descision. Either to keep the language "clean", or because it would "make a mess" of the implementation. Or een because there might be absolute technical obstaccles, e.g. it would break something that is mandated by the C++ standard. [End of speculation]

 

Fact: As long as we do not need to interoperate with C there are perfectly viable alternatives in C++.

 

Opinion: The g++ folks made the right decision.

 

Open end: You've made me realize I should check up on if lambdas are reentrant. Just another thing on the long list.. (-:

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

Last Edited: Sun. Jul 23, 2017 - 08:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> And yes, with this I have of-course thrown the C interoperability with C (which I believe was one of  Georg-Johanns requirements) overboard.

 

It's not only C, it's also C++ if you cannot change existing stuff or are bound to specific C++ versions.  So to say requirements that are not uncommon in the real world and byond trivial projects.

 

 

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know this is an abomination, but maybe some kind of global stack object, where we could store the stuff the lambda needs (including a pointer to the lambda itself) could work and be reentrant?

 

I wrote something very basic, the actual stack would be a very complicated class, that I don't know how to write or even if it can be written...

 

#include <stdio.h>

typedef void (*handler_t)(void);

extern void run_handler (handler_t);

// extremely basic stack class
class stack_c {
#define STACK_SIZE 64
    int stack[STACK_SIZE];
    int top_of_stack = 0;
    public:
        int push (int i){
            stack[top_of_stack] = i;
            if (++top_of_stack == STACK_SIZE) top_of_stack = 0;
            return 0;
        }
        int pop (){
            if (--top_of_stack == -1) top_of_stack = STACK_SIZE-1;
            return stack[top_of_stack];
        }
} Stack;

// Execute `handler' or do whatever we can do with a void(*)(void) function.
void run_handler (handler_t handler)
{
    handler();
}

static void magic (int,                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      int);

int main (void)
{
    magic (2,3);
    return 0;
}

// Code above should be the same in C++
// In particular, a "handler_t" is a void(*)(void) function.
// Code below may be adjusted to whatever C++ which can perform the same magic.

void magic (int c, int d)
#define CALL(func, var) Stack.push(var)?func:func
{
    auto h = []
    {
        printf ("Value = %d\n", Stack.pop());
    };
    run_handler (CALL (h, c));
    run_handler (CALL (h, d));
}

 

Last Edited: Mon. Jul 24, 2017 - 12:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
it's also C++ if you cannot change existing stuff or are bound to specific C++ versions

I'm not entirely clear on what you mean by that. Could you sketch any examples?

 

Let me try to explain what I do see, and then you might help me follow you better:

Assuming we've left C interoperability out then "Existing stuff" must be C++. And since C++ has never supported nested functions your example using that can not be that "existing stuff".

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks to that C vs. C++ challenge, I was able to create a very convoluted blinky program. It blinks 1 time, pause, 2 times, pause... 5 times and repeat. If you're interested and manage to navigate through the code, you will notice that the lambda function calls itself recursively via a pointer to itself passed via the "stack" class I created.

 

I learned a lot writing this quite messy program, actually. A bit about C++ templates, a bit about the C "comma" operator. Nice. Thanks for the challenge.

 

Here is the code, it's supposed to run on an Arduino type board (ATMega with LED on pin B5).

 

#include <stdlib.h>
#include <avr/io.h>

#define F_CPU 16000000UL
#include <util/delay.h>

typedef void (*handler_t)(void);

extern void run_handler (handler_t);


// extremely basic stack class
class stack_c {
    #define STACK_SIZE 64
    void* stack[STACK_SIZE] = {0};		// init stack to null pointers
    int top_of_stack = 0;
    public:
    template <class T>					/* In this way, push can take different types as argument */
    int push (T arg){
        free(stack[top_of_stack]);		//delete whatever was in this stack position, if null do nothing
        stack[top_of_stack] = calloc(1, sizeof(T));
        *(T*)stack[top_of_stack] = arg;
        if (++top_of_stack == STACK_SIZE) top_of_stack = 0;
        return 0;
    }
    void* pop (){
        if (--top_of_stack == -1) top_of_stack = STACK_SIZE-1;
        return stack[top_of_stack];
    }
} Stack;

// Execute `handler' or do whatever we can do with a void(*)(void) function.
void run_handler (handler_t handler)
{
    handler();
}

static void magic (int);

int main (void)
{
    while (1) 
        magic (5);
}

// Code above should be the same in C++
// In particular, a "handler_t" is a void(*)(void) function.
// Code below may be adjusted to whatever C++ which can perform the same magic.

static void magic (int c)
{
    handler_t h = []
    {
        handler_t this_func = *(handler_t*)Stack.pop();
        int this_int = *(int*)Stack.pop();
        //recursively call this function until "captured" variable reaches 1
        if (this_int > 1) {
            // note that I'm using the comma operator to push "capture" variables to the stack
            // before passing the argument to run_handler()
            run_handler ((Stack.push(this_int-1), Stack.push(this_func), this_func));
        }
        // blink this_int times, then pause 1 second
        DDRB |= 1<<PB5;
        for(;this_int;this_int--){
            PORTB |= 1<<PB5;
            _delay_ms(200);
            PORTB &= ~(1<<PB5);
            _delay_ms(200);
        }
        DDRB &= ~(1<<PB5);
        _delay_ms(1000);
    };
    run_handler ((Stack.push(c), Stack.push(h), h));
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the tutorial, very well written!

 

I've used lambdas in C# some, in very simple applications and found them to be very powerful and wanted to start using them in embedded projects now that C++11 supports them.

Happy Trails,

Mike

JaxCoder.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks so much for the explanation.  In typical (lexically scoped) functional languages variables outside of the scope of the lambda are captured automatically by the compiler.  I wonder why this is explicit in C++.  Any idea?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:

The simplest syntax for a lambda is

[] (parameter-list) { body }

where parameter-list and  body are the same as for a normal function. The square brackets are at the start of a lambda, and can contain a so called capture list. I won't go into that now, since we dont need any capture list for the example. But the square brackets are always needed - they serve to signal the start of a lambda, even if the capture list is empty.

 

Well, this is not exactly the "simplest" syntax. If your lambda has no parameters you can omit even the `()` part (however strange it might seem at the first signt)

 

[] { /* body */ }

This is the "simplest" syntax for a lambda expression.

 

JohanEkdahl wrote:

and there is your anonymous function! It satisfies the type definition for the function pointer we have as the second parameter to the applyToElements() so we can pass this lambda expression as an actual parameter!

 

More precisely, it is convertible to the plain function pointer, as long as the capture clause is empty. The moment your lambda begins to capture anything, it can no longer be used in place of a plain function pointer.

 

JohanEkdahl wrote:
If the different sortings are only needed in one place, lambdas will allow us to pass the relatively simple code for the comparison directly at the call to sort(), and there will be no need to write explicit and named comparison functions. The syntax for the lambda gets a wee bit more complicated since we need to state a return type also:

 

[] (parameter-list) -> return-type { body }

 

It is unclear why you claim that "we need to state a return type also". Why, really? If we don't state the return type, it will be derived from `return` statements. There's no need to state it explicitly in your example at all. You can do it if you wish to, but there's no "need".

 

The only cases when you might be forced to do it explicitly is when 1) you are not happy with automatically derived type, and 2) when you have multiple `return` statements that contradict each other (thus making automatic derivation ambiguous).

 

Dessine-moi un mouton

Last Edited: Tue. Jun 4, 2019 - 06:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:

Can you "return" lambdas, if a function returns a pointer to a function?

 

Yes, as long as your lambda is convertible to the function pointer type, i.e. its capture clause is empty.

 

Note the obvious concern here: is it valid to return such a pointer from a function, when the lambda object itself (officially called closure object) is local to the function? Clearly, the closure object gets destroyed as the function exits, but the pointer gets returned. The answer is: yes, it is valid (see https://stackoverflow.com/questions/8026170/lifetime-of-lambda-objects-in-relation-to-function-pointer-conversion )

 

JohanEkdahl wrote:

Have you tried? ;-)

 

A question like this cannot be answered by "trying", due to the issue I mention above. This is not a matter of the "compilability" of the code. This is a matter of whether the behavior is defined. This can only be answered by digging through the standard. And the answer is yes.

Dessine-moi un mouton

Last Edited: Tue. Jun 4, 2019 - 06:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

JohanEkdahl wrote:

You can refer to statically allocated variables, but do understand that this is not a capture. If you define a lambda that refers to a global, then change the global and then invoke the lambda, it will run using the new value of the global.

A capture  works the other way around. The value is "bound" when the lambda is defined.

 

That is true. However, starting from C++14 you can use init-captures. which can be used to capture any expression's value

 

static int a = 42;

auto f = [captured_a = a]()
{
  // Here you can use 'captured_a'
}

Dessine-moi un mouton

Last Edited: Thu. Jun 6, 2019 - 01:39 AM