C++23:标准库模块(一)
Preface
随着LLVM 19 release的发布,标准模板库STL终于可以使用import
关键字进行导入了。这一特性本该随着C++20 Modules特性的落地而落地,但是由于编译器御三家的进展缓慢而拖到了今天。
值得注意的是,即便目前MSVC/LLVM已经完全支持1了STL modules,但是它的大规模应用仍然有种遥不可及的味道。目前C++构建系统的事实标准,cmake对modules的支持仍有限:
- 仅支持Ninja/MSVC构建
- 不支持STL modules或者其他由编译器提供的modules
此外,C++构建系统的后起之秀xmake,实现了较好的modules支持,供参考:xmake: C++20 modules。
工具方面,LLVM附带的一些C++ tools对STL modules的支持可以说比较完善:语言服务器clangd支持从std
模块中补全、查看以及重构;格式化工具clang-format
也可以对import
语句进行处理;静态检查工具clang-tidy
在某些情况下会出现错误,但是大部分情况是可以运行的。
本文主要介绍如何初步使用STL modules,以及clangd
工具的配置。
Getting Started
测试源码
本文使用单文件进行测试,代码如下。
// main.cpp
import std;
namespace views = std::ranges::views;
auto main(int c, char** v) -> int
{
auto vec = std::vector<int> { 1, 2, 3, 4, 5 };
auto gen = vec | views::drop(1) | views::reverse | views::filter([](auto&& n) -> bool { return n % 2 == 0; });
std::print("{}\n", gen);
return 0;
}
使用STL Modules编译
测试环境:
- LLVM 19.1.5 (aarch64), installed by Homebrew.
- Visual Studio Code
生成BMI文件
BMI(Built Module Interface)文件是LLVM支持的可导入模块单元的预编译结果,它的常见后缀名通常是.pcm
。
LLVM 19虽然完整实现了STL modules,但是并没有提供预编译好的BMI文件。因此在使用import std;
之前,必须先编译STL modules:
clang++ -std=c++23 \
/opt/homebrew/Cellar/llvm/19.1.5/share/libc++/v1/std.cppm
-Wno-reserved-identifier -Wno-reserved-module-identifier \
--precompile -o std.pcm
这条命令将会在当前目录下生成std.pcm
文件,供main.cpp
使用。注意std.cppm
文件位置即可。
编译main.cpp
使用这条命令编译main.cpp
:
clang++ -std=c++23 main.cpp -o main -fmodule-file=std=std.pcm -fprebuilt-module-path=.
其中,-fmodule-file=<module-name>=<BMI-file>
指定了导入的模块名,以及BMI文件名,-fprebuilt-module-path
指定了BMI文件的搜索路径。
如果编译成功,则可以运行./main
查看结果。
配置语言服务器clangd
本文通过vscode调用clangd
。
通过vscode-clangd插件传递给clangd
的命令行参数如下:
{
"clangd.arguments": [
"-j=6",
"--log=info",
"--pch-storage=memory",
"--enable-config",
"--header-insertion=never",
"--clang-tidy",
"--completion-style=detailed",
"--background-index",
"--all-scopes-completion",
"--pretty",
"--experimental-modules-support"
],
}
需要注意的是,--experimental-modules-support
必须存在,否则clangd
可能无法识别import
关键字;此外--header-insertion
的值最好设置为never
,避免头文件自动插入导致clangd
解析失败。
除此之外,项目配置文件.clangd
必须存在,且-std=c++23
选项必须存在,不然clangd
可能会无法识别STL modules。
--- # .clangd
CompileFlags:
Add:
- -xc++
- -std=c++23
- -fbuiltin-module-map
- -fprebuilt-module-path=.
- -fmodule-file=std=std.pcm
除此之外,后续-f
选项也是必须的:-fbuiltin-module-map
为clangd
提供了STL modules的内容信息,缺少此项会导致clangd
可以识别到std
是有效的模块,但是无法生成相关的补全建议,也无法通过hover hints查看STL类和函数的相关信息;后两者在上一步中已经提到过,此处不再赘述。
优势与缺点
Modules无疑是便利的,仅以STL而言,几乎所有的#include
都可以被一条import
语句所替换。而且由于BMI的存在,编译时长必然存在明显的下降,这对大型项目而言无疑是重大利好。
目前Modules自身的诸多限制,例如:
- 编译器方面,不同编译器生成的预构建模块无法互相识别,类似于C++ ABI一样的分裂问题;
- 构建系统方面,截止到CMake v3.30,依然只能有限地支持Ninja/MSVC,且配置复杂,不够实用;
- 自身语法问题,不支持导出宏,不能很方便地和预处理器协作;
以上这些问题,使其完全替代头文件或者预处理器,还有很长的路要走。
工具方面,clangd
作为目前唯二的C++ LSP server之一2,它必须依赖module map才可以查找模块的内容并提供相关信息,而LLVM目前还不能根据模块的编译信息自动生成module map备用,这导致clangd
的使用也受到了一定的限制。相关讨论见clangd discussion #1979。而且目前它还存在一些其他的bug,例如无法为import
关键字正确提供基于语义的高亮。
总的来说,STL modules还不太能满足笔者的期望,期待它的后续发展。