Home > Software engineering >  Why does sending in argument as lambda function not work while sending it as normal function pointer
Why does sending in argument as lambda function not work while sending it as normal function pointer

Time:08-24

I have been working on libcurl library and there I need to provide the API with a callback function of how it should handle the recieved data. I tried providing it with this callback function as a lambda and it gave me Access violation error, while when I give the same function as a function pointer after defining the function somewhere else it works fine! I want to know what is the difference between the 2 as I thought they were the same thing.

The code used in the following part comes from https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html which is the documentation for libcurl.

Here is the code where I send it the lambda function (This gives access violation error):

int main() {
    // Irrelevant initial code...

    struct memory {
        char* response;
        size_t size;
    } chunk = { 0 };
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*) &chunk);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, // I define a lambda function for the callback...
        [](void* data, size_t size, size_t nmemb, void* chunk) -> size_t {
            cout << "This function will never get called.. :(" << endl;
            size_t realSize = size * nmemb;
            memory* mem = (memory*) chunk;
    
            char* ptr = (char*) realloc(mem->response, mem->size   realSize   1);
            if (ptr == NULL) return 0;
    
            mem->response = ptr;
            memcpy(&(mem->response[mem->size]), data, realSize);
            mem->size  = realSize;
            mem->response[mem->size] = 0;
    
            return realSize;
        });
    
    CURLcode success = curl_easy_perform(handle);
    return 0;
}

Here the lambda function was never called thus the line This function will never get called.. :(never gets displayed in console. It gives access violation error inside the libcurl library in the file called sendf.c in line number 563.

Now here is the same exact thing but I define the function outside:

struct memory {
    char* response;
    int size;
};

// defined the callback function outside...
size_t cb(void* data, size_t size, size_t nmemb, void* userp)
{
    cout << "Working!" << endl;
    size_t realsize = size * nmemb;
    memory* mem = (memory*)userp;

    char* ptr = (char*) realloc(mem->response, mem->size   realsize   1);
    if (ptr == NULL)
        return 0;

    mem->response = ptr;
    memcpy(&(mem->response[mem->size]), data, realsize);
    mem->size  = realsize;
    mem->response[mem->size] = 0;

    return realsize;
}

int main() {
    // Irrelevant initial code...

    memory chunk = { 0 };
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*) &chunk);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, cb);

    CURLcode success = curl_easy_perform(handle);
    return 0;
}

This one worked and it displayed Working! in the console.

I cannot understand why this 2 are different and why one of them works and the other one does not. Also is it possible to make this work with the lambda function approach as I think it looks much more neater.

CodePudding user response:

Edit

Major props to @user17732522 for reminding me you can get the same effect without all the drama by simply using as the prefix to your lambda. Way too late for me to be authoring reasonable answers. SryI totally spaced that.

Original

The curl_easy_setopt takes a variable argument stack, and peels them apart to their subcomponents depending on what option is being established. If you peek deep enough into the curl headers you'll find:

CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);

The variable arguments are important. That will take damn near anything, and expect it as its native type. So... what is the native type of that lambda. A "cheater" way to tell is by using a generic template with a pretty-print option to announce what type was received in the expansion (it sounds confusing, it won't be in a minute, I promise):

#include <iostream>

template<class T>
void foo(T&&)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

int main() 
{
    foo([](void *, size_t , size_t , void *) -> size_t
    {
        return size_t();
    });

    return 0;
}

Output

void foo(T &&) [T = (lambda at main.cpp:11:9)]

So, yeah, it's happily shoving a 'lambda' into the variable arg list. Legally casting to the equivalent function pointer type (which we can do because the lambda is non-capturing), gives us:

#include <iostream>

template<class T>
void foo(T&&)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

int main() 
{
    foo(static_cast<size_t (*)(void *, size_t, size_t, void *)>(
        [](void *, size_t , size_t , void *) -> size_t
        {
            return size_t();
        }));

    return 0;
}

Output

void foo(T &&) [T = unsigned long (*)(void *, unsigned long, unsigned long, void *)]

Now an actual function pointer is being shoved into the arg list, which is what is expected by CURLOPT_WRITEFUNCTION

Note: Non-capturing lambdas can be used in a function pointer context implicitly if the context calls out the function pointer expectation. For example, the following requires no casting; conversion is implicit because the lambda is non-capturing, and therefore qualifies, and the context specifically calls for function pointer that matches accordingly:

#include <iostream>

void bar(size_t (*)(void *, size_t, size_t, void *))
{
}

int main() 
{
    bar([](void *, size_t , size_t , void *) -> size_t
        {
            return size_t();
        });

    return 0;
}

The short answer is: curl_easy_setopt variable arguments were consuming your lambda as a lambda; not as a function pointer. If you want a function pointer (and you do) in that case, you need to 'coax' it into submission.

  • Related