没有从 'lambda' 到 'void ...' 的可行转换

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

我需要给一个函数另一个函数或 lambda 作为参数,这或多或少是可行的。 当我尝试在 C++14 中为 lambda 定义捕获时,就会出现错误。您可以在此处查看示例代码:

// 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);});

如您所见,部分问题是我无法更改部分代码。有什么我可以做的,或者我错过了什么,还是情况没有希望了?

这是我得到的错误:

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

函数不能有捕获。 Lambda 可以,这意味着它们不是函数。没有捕获的 lambda 可以转换为函数指针,但有捕获的 lambda 不能。

此代码:

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

实际上等同于:

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

并且无法将 MyLambda 对象视为函数指针,因为它不是函数,它实际上是一个包含变量的对象。

如果 lambda 没有 捕获,您可以很容易地构造一个包装函数,如下所示:

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

然后你可以做

mySvgElement.onclick(MyLambda_wrapper);

这实际上就是编译器所做的。但是,这不适用于捕获,因为包装函数需要知道要放入捕获中的值。

Lambda 不允许您使用以前不能做的语言做任何新事情。它们只是做您已经可以做的事情的捷径。

不过,您确实有一些选项可以存储角度:

  • 如果角度始终为 15,则可以硬编码 15。

  • 如果所有形状的角度都相同,可以将其设为全局变量。

  • 通常,库会在其数据结构中留下一些成员供应用程序使用,通常称为 void *contextvoid *userdata。您可以将角度存储在该变量中:

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

    如果你有不止一个,你需要存储一个结构指针,并记得在元素被销毁时释放它:

    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;});
    
  • 您可以将角度存储在您自己的全局中 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);});