C++17 详解 10

本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。

3.4 constexpr lambda 表达式

lambda 表达式自 C++11 引入后,就成为了现代 C++ 必不可少的一部分。C++11 的另一个重要特性是“常量表达式”——主要通过 constexpr 声明。C++17 允许这两个元素共存——你可以在一个常量表达式上下文中调用 lambda 。

C++11/14 里,下边的代码不能通过编译:

1
2
auto SimpleLambda = [] (int n) { return n; };
static_assert(SimpleLambda(3) == 3, "");

-std=c++11 flag 在 GCC 下编译会报如下错误:

1
2
error: call to non-'constexpr' function 'main()::<lambda(int)>'
static_assert(SimpleLambda(3) == 3, "");

但是,换成 -std=c++17 代码就可以编译通过了!这是因为 C++17 里遵循标准 constexpr 函数规则的 lambda 表达式被隐式地声明为了 constexpr

这句话什么意思呢?有什么限制呢?

摘自 Standard - 10.1.5 constexpr 指示符 [dcl.constexpr]

译注:完整版的 C++17 标准草案发布在这里——有 1622 页。

一个被定义为 constexpr 的函数应满足如下条件:

  • 不能是虚函数(13.3);
  • 返回类型应是一个字面类型(译注:原文为 literal type,不是字面值的意思,标准里有明确的、复杂的定义。);
  • 每一个参数的类型均应是字面类型;
  • 函数体应是 = delete= default,或是一个不包含如下内容的复合语句:
    • 汇编实现,
    • goto 语句,
    • 标识符标签(译注:即同 goto 配合使用的 val:),
    • try 块,
    • 非字面类型的,或静态的,或线程存储周期(译注:即 thread_local)的,或未进行初始化的变量的定义。

译注:如我之前吐槽,C++ 的一大精通难点体现在,一个小小的特性关键字背后,是嵌套了 N 层的、晦涩的、没有任何表征的概念定义。constexpr 可见一斑。

实践角度讲,如果想要你的函数或 lambda 在编译期被执行,函数体内不应调用任何非 constexpr 代码。比如,不能动态分配内存。

lambda 也可以显式地声明为 constexpr

1
auto SimpleLambda = [] (int n) constexpr { return n; };

如果你违反了 constexpr 函数规则,会触发编译错误。

1
2
3
4
5
auto FaultyLeakyLambda = [] (int n) constexpr {
int *p = new int(10);

return n + (*p);
};

new 操作符是非 constexpr 的所以上边代码不会编译通过。

constexpr lambda 在结合了即将到来的 C++20 的 constexpr 标准算法后将会是一个伟大特性。

扩展:本修改提案:P0170

3.5 嵌套命名空间

命名空间允许把类型和函数分组到不同的逻辑单元。

比如,通常会把一个叫 XY 库里的类型和函数存储到一个叫 xy 的命名空间。如下所示,有一个 SuperCompressionLib 命名空间,它暴露了两个叫 CompressDecompress 的函数:

1
2
3
4
namespace SuperCompressionLib {
bool Compress();
bool Decompress();
}

如果你有两层或更多嵌套的命名空间,事情就变得有趣了。

1
2
3
4
5
6
7
8
9
10
11
12
namespace MySuperCompany {
namespace SecretProject {
namespace SafetySystem {
class SuperArmor {
// ...
};
class SuperShield {
// ...
};
} // SafetySystem
} // SecretProject
} // MySuperCompany

C++17 里,嵌套命名空间可以以一种更紧凑的方式书写:

1
2
3
4
5
6
7
8
namespace MySuperCompany::SecretProject::SafetySystem {
class SuperArmor {
// ...
};
class SuperShield {
// ...
};
}

这种语法令人愉悦,对有 C# 或 Java 经验的开发者也更容易使用。

C++17 里,标准库的许多地方也以这种新的嵌套命名空间特性被“压缩了”:

比如 regex 库。

C++17 里它的定义如下:

1
2
3
4
namespace std::regex_constants {
typedef T1 syntax_option_type;
// ...
}

C++17 之前同样的代码被声明为:

1
2
3
4
5
6
namespace std {
namespace regex_constants {
typedef T1 syntax_option_type;
// ...
}
}

上边的嵌套声明出现在 C++ 规范,但每一个 STL 实现可能都会不同。

扩展:本修改提案:P4230

3.6 编译器支持

略。

评论