如何为函数对象重新分配Lambda表达式?解决auto的const绑定问题
咱先把你遇到的问题根源揪出来——你以为是auto给函数对象加了const导致赋值失败,但其实核心原因是每个Lambda表达式的类型都是独一无二的!用auto声明的comp_2被推导成第一个Lambda的专属类型,而你后面想赋值的第二个Lambda是完全不同的另一种类型,自然会编译报错,跟mutable半毛钱关系都没有(mutable只是让Lambda内部能修改捕获的变量,不影响它的类型唯一性)。
当你写auto comp_2 = [&ref](...) mutable {...};时,编译器会给这个Lambda生成一个匿名的、独有的类类型,comp_2就是这个类的实例。而你后续尝试赋值的第二个Lambda,编译器会生成另一个完全不同的匿名类类型——两种类型不兼容,赋值操作自然编译失败。
反观comp_1用std::function声明就没问题,因为std::function是类型擦除容器,它能容纳任何符合签名的可调用对象(不管具体类型是什么),这就是它和auto推导的本质区别。
最直接且符合你需求的优雅方案,就是把comp_2也声明为std::function类型,和comp_1保持一致。如果想保留auto的简洁性,也可以用auto结合std::function的推导构造(C17及以上支持),或者用C20的std::move_only_function(如果不需要拷贝语义的话)。
另外有两种进阶方案,但对你的场景来说属于过度设计:
- 用
std::variant存储不同的Lambda类型,但需要额外处理调用逻辑 - 自定义包装类手动实现类型擦除,但
std::function已经帮你做好了这件事
把comp_2的声明改成std::function,就能完美解决赋值问题,同时不影响后续priority_queue的使用:
#include <functional> #include <memory> #include <queue> #include <random> #include <utility> using namespace std; void f1(); int main() { srand(0); f1(); return 0; } void f1() { using my_pair = pair<int,int>; int ref = 2; function<bool(const my_pair &,const my_pair &)> comp_1 = [&ref](const my_pair &LHS, const my_pair &RHS) {return LHS.first-ref > RHS.first-ref;}; comp_1 = [&ref](const my_pair &LHS, const my_pair &RHS) {return LHS.first < RHS.first;}; // So far so good. // 把auto改成std::function,和comp_1同类型 function<bool(const my_pair &,const my_pair &)> comp_2 = [&ref](const my_pair &LHS, const my_pair &RHS) mutable {return LHS.first-ref > RHS.first-ref;}; // 现在赋值完全没问题! comp_2 = [&ref](const my_pair &LHS, const my_pair &RHS) mutable {return LHS.first < RHS.first;}; priority_queue<my_pair, vector<my_pair>, decltype(comp_2)> myHeap(comp_2); for (int i=0; i<10; i++) myHeap.emplace(rand()%10,rand()%20); while (!myHeap.empty()) { printf("<%d,%d>\n",myHeap.top().first,myHeap.top().second); myHeap.pop(); } }
如果你用的是C++17及以上版本,也可以用auto结合std::function的推导构造,写起来更简洁:
auto comp_2 = std::function{[&ref](const my_pair &LHS, const my_pair &RHS) mutable {return LHS.first-ref > RHS.first-ref;}}; // 后续赋值依然有效
为什么mutable没用?因为mutable修饰的是Lambda的调用运算符——默认情况下Lambda的operator()是const成员函数,不能修改捕获的变量;加了mutable后,operator()变成非const,允许修改捕获变量,但这并不会改变Lambda的类型本身,也不会让不同的Lambda类型变得兼容。
内容的提问来源于stack exchange,提问作者user3813057

