C++17常用新特性(六)---lambda表达式的扩展

2022-04-13 15:30:06 浏览数 (1)

从C 11起就引入了lambda表达式,C 14又对其进行了丰富,开始支持使用泛型lambda。到现在的C 17 lambda的功能又进行了扩展。在C 17新特性中,主要支持了以下两种场景:

  • 在常量表达式中使用
  • 需要对当前对象的拷贝时使用,如不同的线程需要创建不同的对象。

1 constexpr lambda表达式

从C 17开始,lambda表达式会尽可能的隐式声明constexpr,在任何只使用有效的编译期上下文的lambda都有可能被用于编译期。这些上下文环境主要包含的场景有:只使用字面变量、没有静态类型、没有虚函数、没有异常捕获及new/delete的上下文环境。如在下面的代码中,因为在lambda中使用了static,表达式将会失去constexpr的能力。

代码语言:javascript复制
int main(){
    auto squared2 = [](auto val) {
        static int calls = 0;
        return val*val;
    };
    std::array<int, squared2(5)> a; 
    std::cout << squared2(5) << 'n'; 
    return 0;
}

如上代码片段中,第6行代码将lambda中计算的值当做array数组的大小在编译时将会报错。因为在lambda中声明了一个static类型的变量,那么表达式也将失去constepr的能力,既不能在编译器使用。但是它依然可以在运行期使用,试着将第6行代码段进行注释,那么代码可以继续编译且打印出计算结果。

同样的,如果在上面代码中显示定义成constexpr。那么编译时程序将会报错如下:

如此,按照上面上面编译场景,在确认一个lambda表达式是否可用于编译期时就可以在表达式中使用constepr进行判断。如从C 17起,就可以按照以下方式使用:

代码语言:javascript复制
int main(){
    auto squar = [](auto val) constexpr {
        return val*val;
    };
    return 0;
}

如果要在表达式中指明返回值类型,可以按照如下方式编写代码:

代码语言:javascript复制
int main(){
    auto squar = [](auto val) constexpr -> int {
        return val*val;
    };
    return 0;
}

在看下面两种lambda表达式中使用constepr的代码段:

代码语言:javascript复制
int main()
{
    auto squar1 = [](auto val) constexpr {
        return val*val;
    };
    constexpr auto squar2 = [](auto val) {
        return val*val;
    };
    return 0;
}

如上squqr1是在表达式中使用constexpr,squar2则是在变量前使用constexpr,他们表达的含义是不同的,第一种写法是在编译器执行,第二种表示的是在编译器就会对变量进行赋值。如果想同时使用可以按照下面的方式编写代码:

代码语言:javascript复制
constexpr auto squar2 = [](auto val) constexpr{
        return val*val;
    };

2 constexpr lambda的使用

下面的例子主要演示了在lambda中使用constexpr,代码分别在编译期和运行期调用lambda的场景,代码如下:

代码语言:javascript复制
auto hash = [](const char* str) {
    std::size_t hash = 5381;
    while (*str != '') {
        hash = hash * 33 ^ *str  ; 
    }
    return hash;
};

emum hashV{
    hash(cat),
    hash(dog),
    hash(mow),
    hash(sheep)
};

int main(int argv,char * argc[])
{
    switch(argc[2]){
        case hash(cat):
            break;
        case hash(dog):
            break;
        case hash(mow):
            break;
        case hash(sheep):
            break;
    }
    return 0;
}

在上面的代码中,switch的条件是在运行时执行,case是在编译时执行。实际上在上面程序里面还可以使用函数,如下面的代码:

代码语言:javascript复制
auto hashed = [](const char* str, auto combine) {
  std::size_t hash = 1437;
  while (*str != '') {
  hash = combine(hash, *str  ); 
  }
  return hash;
};

在使用时按照如下方式只用即可:

代码语言:javascript复制
constexpr std::size_t hv1{hashed("wine"), [](auto h, char c) {return h*33   c;})};

3 向 lambda 传递 this 的拷贝

在C 11或者C 14中,如果要捕获this,可以通过值或者引用的方式进行。编写代码时可以按照下面的方式进行:

代码语言:javascript复制
class CType {
private:
std::string name;
public:
CType(const std::string &_name):name(_name){
    std::cout<<"CType->"<<name<<endl;
}
void foo() const
{
    auto Type1 = [this] {std::cout << this->name << endl;};
    auto Type2 = [=] {std::cout << name << endl;}; 
    auto Type3 = [&] {std::cout << name << endl;};
    Type1();
    Type2();
    Type3();
    
}
};
int main()
{
    CType stType{"qwqw"};
    stType.foo();
    return 0;
}

如上代码所示程序将会输出正确的结果,但是也存在着问题,既当lambda的生命周期更长时,可能不会得到正确的结果。但是在C 14中,提出了一个解决方案,代码可以修改成如下所示:

代码语言:javascript复制
void foo() const
{
    auto Type1 = [thisCopy=*this] {std::cout << thisCopy.name << endl;};
    Type1();
}

在C 17中,就可以显示的使用*this进行捕获,代码如下修改:

代码语言:javascript复制
void foo() const
{
    auto Type1 = [*this] { std::cout << name << endl; };
    Type1();
}

当然,也可以在捕获this的时候捕获其它对象,如:

代码语言:javascript复制
auto Type1 = [&,*this] { std::cout << name << endl; };

4 相关背景

constexpr lambda 由 Faisal Vali、 Ville Voutilainen和 Gabriel Dos Reis 在https://wg21.link/n4487中首次提出的。但是最终被接受的正式谈是由Faisal Vali、Jens Maurer、 Richard Smith 发表于https://wg21.link/p0170r1。

0 人点赞