Function as Parameter/Argument (lambda function)

I'm getting into ESP as WebServer as began using the ESPAsyncWebServer Library. One method I don't quite understand is the server.on()

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
 });

How are you able to pass a full function as a parameter? All I know is to create a function separately and then call it later on or pass it as a reference into a method or another function.

For now I've been using it as the examples showed to me but I have no idea how it works. I thought it would be interesting to be able to use in my own codes.

I looked into the library to try and figure out how to use it and create it myself and quickly got confused with all the source files. I never saw an example like this before and can not find it explained on the forum anywhere

Mostly im interested in how they pass the function with brackets and everything. Ive seen something similar in js but didn't understand it either

If anyone can help me understand this, that would be sweet!
Thank you.

Google "C++ lambda function"

As @anon73444976 noted, that is a lambda or anonymous function. BTW, it's not absolutely necessary use that construct in this case. In fact, if the callback function is long and complex, it might be more readable to pass a pointer to a free function instead.

@ TheMemberFormerlyKnownAsAWOL @ gfvalvo
Thank you very much, that was exactly what I was looking for and if I understand correctly this:

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
 });

is the same as this:

 server.on("/", HTTP_GET, sendIndex);

  ...

 void sendIndex(AsyncWebServerRequest *request) {
   request->send_P(200, "text/html", index_html, processor);
 }

C++ never ceases to surprise me. It keeps getting more complicated and cooler

Pretty much, apart from the different scope, which I suspect is impossible to express, other than with a lambda.

We're all still learning!
(I cannot get my head around templates - they're just plain ugly)

why would you need to pass a complete function definition? why not pass a function ptr?

In this case: because it makes the code more readable by having the callback definition right next to the place where you register it. If the handler were a free function, you would have to scroll or navigate to its definition to see what it's doing. For a simple one-line function, it's easier to see what's going on if it's defined inline with the code it's related to.

Another great benefit of lambda functions is that they can capture variables from the surrounding scope. This is very useful if you need to pass data to a callback, for example. Otherwise you would have to use global variables or (shivers) add a void * argument to all callbacks.

Also, you're not passing “a complete function definition”. It's still just a function, the definition is compiled and placed somewhere in the binary, it is not “passed” at runtime. You can just pass a function pointer to it to another function (at least if the lambda doesn't capture any data).

As another motivating example, consider sorting arrays with a custom compare function (a free function vs an inline function):

Free function

int compare_c_string_lengths(const void *a, const void *b) {
    const char *str_a = *reinterpret_cast<const char *const *>(a);
    const char *str_b = *reinterpret_cast<const char *const *>(b);
    return std::strlen(str_a) - std::strlen(str_b);
}

void sort_strings_by_length_c(const char *strings[], size_t n) {
    std::qsort(strings, n, sizeof *strings, compare_c_string_lengths);
}

Lambda

void sort_strings_by_length_cpp(std::span<std::string_view> strings) {
    auto cmp = [](auto a, auto b) { return a.length() < b.length(); };
    std::ranges::sort(strings, cmp);
}

There are also cases where you wouldn't be able to use a free function at all. For example:

void filter_strings_longer_than(vector<string_view> &strings, size_t len) {
    auto predicate = [=](auto s) { return s.length() > len; };
    erase_if(strings, predicate);
}
vector<string_view> strings {"a", "ab", "abc", "abcd", "abcde"};
filter_strings_longer_than(strings, 3); // → {"a", "ab", "abc"}

Notice how the predicate depends on the len argument.

Or, specifically for AWOL, let's sprinkle in some angle brackets to generalize it to any container type:

template <template <typename, typename...> class Container, typename... Opts>
void filter_strings_longer_than(Container<string_view, Opts...> &strings, size_t len) {
    auto predicate = [=](auto s) { return s.length() > len; };
    erase_if(strings, predicate);
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.