Home > Enterprise >  no viable conversion from 'lambda' to 'void ...'
no viable conversion from 'lambda' to 'void ...'

Time:11-13

I need to give a function another function or lambda as a parameter, and this works, more or less. There is an error as soon as I try to define a capture for a lambda in c 14. You can see the sample code here:

// this is part of a library (I cannot change it)
class SVGElement {
   
    //...
    
    public:    
    void onclick(void (*handler)(SVGElement *)) {
        handler(this);
    }
    
    void rotateBy(int angle) {/*...*/}
    
    //...
};



// my code

SVGElement mySvgElement = SVGElement();

// this works
mySvgElement.onclick([](SVGElement* clicked){clicked->rotateBy(15);});


// as soon as I define a capture, there is an error
int angle = 15;
mySvgElement.onclick([angle](SVGElement* clicked){clicked->rotateBy(angle);});

As you can see, part of the problem is that I cannot change part of the code. Is there anything I can do or am I missing something or is the situation hopeless?

Here is the error I get:

input_line_7:22:22: error: no viable conversion from '(lambda at input_line_7:22:22)' to 'void (*)(__cling_N52::SVGElement *)'
mySvgElement.onclick([angle](SVGElement* clicked){clicked->rotateBy(angle);});
                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
input_line_7:7:25: note: passing argument to parameter 'handler' here
    void onclick(void (*handler)(SVGElement *)) {
                        ^

CodePudding user response:

Functions can't have captures. Lambdas can, which means they aren't functions. A lambda with no captures can be converted to a function pointer but a lambda with captures cannot.

This code:

int angle = 15;
mySvgElement.onclick([angle](SVGElement* clicked){clicked->rotateBy(angle);});

is effectively equivalent to:

int angle = 15;
struct MyLambda {
    int angle;
    void operator()(SVGElement* clicked){clicked->rotateBy(angle);}
};
mySvgElement.onclick(MyLambda{angle});

and there is no way to treat a MyLambda object as a function pointer, because it's not a function, it's actually an object with variables in it.

If the lambda had no captures, you could easily construct a wrapper function like this:

void MyLambda_wrapper(SVGElement* clicked) {
    MyLambda l;
    l(clicked);
}

and then you could do

mySvgElement.onclick(MyLambda_wrapper);

which is effectively what the compiler does. However, this doesn't work with captures, because the wrapper function needs to know what values to put in the captures.

Lambdas don't let you do anything new with the language that you couldn't do before. They are just a shortcut to do things you could already do.

You do have some options to store the angle, though:

  • If the angle is always 15, you can just hardcode 15.

  • If the angle is the same for all shapes, you can make it a global variable.

  • Often, libraries will leave some member in their data structures for the application to use, often called void *context or void *userdata. You could store the angle in that variable:

    int angle = 15;
    mySvgElement.userdata = (void*)angle;
    mySvgElement.onclick([](SVGElement* clicked){clicked->rotateBy((int)clicked->userdata);});
    

    If you have more than one of these, you'd need to store a struct pointer and remember to free it when the element is destroyed:

    mySvgElement.userdata = new my_svg_element_data;
    ((my_svg_element_data*)mySvgElement.userdata)->left_click_angle = 15;
    ((my_svg_element_data*)mySvgElement.userdata)->right_click_angle = 30;
    mySvgElement.onclick([](SVGElement* clicked){clicked->rotateBy(((my_svg_element_data*)clicked->userdata)->left_click_angle);});
    mySvgElement.onrclick([](SVGElement* clicked){clicked->rotateBy(((my_svg_element_data*)clicked->userdata)->right_click_angle);});
    mySvgElement.ondestroy([](SVGElement* destroyed){delete (my_svg_element_data*)destroyed->userdata;});
    
  • You could store the angle in your own global std::unordered_map<SVGElement*, int> click_angle;:

    click_angle[&mySvgElement] = 15;
    mySvgElement.onclick([](SVGElement* clicked){clicked->rotateBy(click_angle[clicked]);});
    mySvgElement.ondestroy([](SVGElement* destroyed){click_angle.erase(destroyed);});
    
  • Related