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 pointer
i = 1; x = 1; l1(); // calls use(0, 1), as if i by copy and x by reference
auto l2 = [i, this] { use(i, x); }; // same as above, made explicit
i = 2; x = 2; l2(); // calls use(1, 2), as if i by copy and x by reference
auto l3 = [&] { use(i, x); }; // captures i by reference and a copy of the this pointer
i = 3; x = 2; l3(); // calls use(3, 2), as if i and x are both by reference
auto l4 = [i, *this] { use(i, x); }; // makes a copy of *this, including a copy of x
i = 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;
}
};
} // namespace
int test_lambda_17_this()
{
//reference: https://en.cppreference.com/w/cpp/language/lambda
S s;
s.f();
// reference: https://github.com/AnthonyCalandra/modern-cpp-features#lambda-capture-this-by-value
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
std::cout << "valueCopy: " << valueCopy() << ", valueRef: " << valueRef() << "\n"; // valueCopy: 123, valueRef: 123
mo.value = 321;
valueCopy();
valueRef();
std::cout << "valueCopy: " << valueCopy() << ", valueRef: " << valueRef() << "\n"; // valueCopy: 123, valueRef: 321
std::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起隐式constexpr
constexpr 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; };
} // namespace
int test_lambda_17_constexpr()
{
// 如果函数调用operator(或generic lambda的特化)为constexpr,则此函数为constexpr
auto 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-lambda
auto 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: 2
static_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函数的要求,则它是隐式constexpr
auto 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.md
std::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