Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

no viable conversion from 'lambda' to 'void …'

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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 *)) {
                        ^

>Solution :

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);});
    
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading