C++17中对lambda表达式新增加了2种features:lambda capture of *this和constexpr lambda
1.lambda capture of *this:
*this:拷贝当前对象,创建副本:捕获*this意味着该lambda生成的闭包将存储当前对象的一份拷贝 。
this:通过引用捕获。
当你需要捕获一个对象的成员变量时,不能直接去捕获成员变量。需要先去捕获对象的this指针或引用。
*this: simple by-copy capture of the current object[=, *this] {}; // since C++17: OK: captures the enclosing by copy
this: simple by-reference capture of the current object[&, this] {}; // C++11: OK, equivalent to [&][&, this, i] {}; // C++11: OK, equivalent to [&, i]
测试代码如下:
namespace {class S {
public:void f(){int i{ 0 };auto l1 = [=] { use(i, x); }; // captures a copy of i and a copy of the this pointeri = 1; x = 1; l1(); // calls use(0, 1), as if i by copy and x by referenceauto l2 = [i, this] { use(i, x); }; // same as above, made expliciti = 2; x = 2; l2(); // calls use(1, 2), as if i by copy and x by referenceauto l3 = [&] { use(i, x); }; // captures i by reference and a copy of the this pointeri = 3; x = 2; l3(); // calls use(3, 2), as if i and x are both by referenceauto l4 = [i, *this] { use(i, x); }; // makes a copy of *this, including a copy of xi = 4; x = 4; l4(); // calls use(3, 2), as if i and x are both by copy}private:int x{ 0 };void use(int i, int x) const { std::cout << "i = " << i << ", x = " << x << "\n"; }
};struct MyObj {int value{ 123 };auto getValueCopy() {return [*this] { return value; }; // C++17}auto getValueRef() {return [this] { return value; }; // C++11}
};class Data {
private:std::string name;public:Data(const std::string& s) : name(s) { }auto startThreadWithCopyOfThis() const {// 开启并返回新线程,新线程将在3秒后使用this:std::thread t([*this] {using namespace std::literals;std::this_thread::sleep_for(3s);std::cout << "name: " << name << "\n";});return t;}
};} // namespaceint test_lambda_17_this()
{//reference: https://en.cppreference.com/w/cpp/language/lambdaS s;s.f();// reference: https://github.com/AnthonyCalandra/modern-cpp-features#lambda-capture-this-by-valueMyObj mo;auto valueCopy = mo.getValueCopy();auto valueRef = mo.getValueRef();std::cout << "valueCopy: " << valueCopy() << ", valueRef: " << valueRef() << "\n"; // valueCopy: 123, valueRef: 123mo.value = 321;valueCopy();valueRef();std::cout << "valueCopy: " << valueCopy() << ", valueRef: " << valueRef() << "\n"; // valueCopy: 123, valueRef: 321std::thread t;{Data d{ "c1" };t = d.startThreadWithCopyOfThis();} // d不再有效t.join();return 0;
}
执行结果如下图所示:
2.constexpr lambda:
在C++17中lambda表达式可以声明为constexpr。constexpr关键字用于在编译时执行计算。
自从C++17起,lambda表达式会尽可能的隐式声明constexpr。也就是说,任何只使用有效的编译期上下文(例如,只有字面量,没有静态变量,没有虚函数,没有try/catch,没有new/delete的上下文)的lambda都可以被用于编译期。
使用编译期上下文中不允许的特性将会使lambda失去成为constexpr的能力,不过你仍然可以在运行时上下文中使用lambda.
为了确定一个lambda是否能用于编译期,你可以将它声明为constexpr.
当我们需要一个lambda表达式为constexpr时,我们最好显式的对lambda的表达式进行声明,当编译期不通过时,编译期会告诉我们哪里做错了。
注意:
(1).如果lambda表达式声明为constexpr,则需要遵循某些规则:如表达式的主体不应包含非constexpr的代码;
(2).如果operator()满足constexpr函数的要求,或generic lambda特化为constexpr,则它始终是constexpr;
(3).如果lambda说明符中使用了关键字constexpr,那么它也是constexpr;
(4).如果lambda的结果满足constexpr函数的要求,则它是隐式constexpr;
(5).如果lambda隐式或显式为constexpr,则转换为函数指针会生成constexpr函数;
(6).如果我们使用编译期lambda初始化一个容器,那么编译器优化时很可能在编译期就计算出容器的初始值.
(7).自从C++17起,如果lambda被显式或隐式地定义为constexpr,那么生成的函数调用运算符将自动是constexpr.
测试代码如下:
namespace {constexpr int addOne(int n) { return [n] { return n + 1; }(); } // reference: https://stackoverflow.com/questions/12662688/parentheses-at-the-end-of-a-c11-lambda-expression
constexpr auto addOne2(int n) { return [n] { return n + 1; }; } // 注:上下两条语句的区别
constexpr auto addOne3 = [](int n) { return n + 1; };auto squared = [](auto val) { // 自从C++17起隐式constexprconstexpr int x{ 10 };return val * x;
};// 为了确定一个lambda是否能用于编译期,你可以将它声明为constexpr
auto squared3 = [](auto val) constexpr { return val * val; }; // 自从C++17起
auto squared3i = [](int val) constexpr -> int { return val * val; };// 自从C++17起,如果lambda被显式或隐式地定义为constexpr,那么生成的函数调用运算符将自动是constexpr
auto squared1 = [](auto val) constexpr { return val * val; }; // 编译期lambda调用
constexpr auto squared2 = [](auto val) { return val * val; }; // 编译期初始化squared2
constexpr auto squared4 = [](auto val) constexpr { return val * val; };} // namespaceint test_lambda_17_constexpr()
{// 如果函数调用operator(或generic lambda的特化)为constexpr,则此函数为constexprauto Fwd = [](int(*fp)(int), auto a) { return fp(a); };auto C = [](auto a) { return a; };static_assert(Fwd(C, 3) == 3);// reference: https://github.com/AnthonyCalandra/modern-cpp-features#constexpr-lambdaauto identity = [](int n) constexpr { return n; };static_assert(identity(123) == 123);constexpr auto add = [](int x, int y) {auto L = [=] { return x; };auto R = [=] { return y; };return [=] { return L() + R(); };};static_assert(add(1, 2)() == 3);static_assert(addOne(1) == 2);static_assert(addOne2(1)() == 2);std::cout << "addOne:" << addOne(1) << ", addOne2: " << addOne2(1)() << "\n"; // addOne:2, addOne2: 2static_assert(addOne3(1) == 2);int v = [](int x, int y) { return x + y; }(5, 4);std::cout << "v: " << v << "\n"; // v: 9// 将一个lambda表达式嵌套在另一个lambda表达式中int v2 = [](int x) { return [](int y) { return y * 2; }(x)+3; }(5);std::cout << "v2: " << v2 << "\n"; // v2: 13// reference: https://learn.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-170// 当函数对象需要去修改通过副本传入的变量时,表达式必须用mutable修饰int m = 0, n = 0;[&, n](int a) mutable { m = ++n + a; }(4);std::cout << "m:" << m << ", n:" << n << "\n"; // m:5, n:0// 如果lambda的结果满足constexpr函数的要求,则它是隐式constexprauto answer = [](int n) {return 32 + n;};constexpr int response = answer(10);static_assert(response == 42);// reference: https://github.com/MeouSker77/Cpp17/blob/master/markdown/src/ch06.mdstd::array<int, squared(5)> arr; // 自从C++17起 => std::array<int, 50>// 如果lambda隐式或显式为constexpr,则转换为函数指针会生成constexpr函数auto Increment = [](int n) {return n + 1;};constexpr int(*inc)(int) = Increment;return 0;
}
执行结果如下图所示:
lambda表达式的最短方式可以写为:[]{} :其没有参数,没有捕获任何东西,并且也不做实质性的执行。当函数对象需要去修改通过副本传入的变量时,表达式必须用mutable修饰。
GitHub:https://github.com/fengbingchun/Messy_Test