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

Source Code

本文使用单文件进行测试,代码如下。

// 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;
}

Compiling with STL Modules Step by Step

测试环境:

  • LLVM 19.1.5 (aarch64), installed by Homebrew.
  • Visual Studio Code

Step 0. Generate the Built Module Interface (BMI, .pcm Files)

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文件位置即可。

Step 1. Compile the 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查看结果。

Step 2. Configure the LSP Server 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-mapclangd提供了STL modules的内容信息,缺少此项会导致clangd可以识别到std是有效的模块,但是无法生成相关的补全建议,也无法通过hover hints查看STL类和函数的相关信息;后两者在上一步中已经提到过,此处不再赘述。

优势与缺点

Modules无疑是便利的,仅以STL而言,几乎所有的#include都可以被一条import语句所替换。而且由于预编译模块的存在,编译时长必然存在明显的下降,这对大型项目而言无疑是重大利好。

但是目前由于Modules自身的限制(不能导出宏定义等),以及构建系统的支持问题,使用它替代传统头文件,或者说预处理器的路还很漫长。

工具方面,clangd作为目前唯二的C++ LSP server之一[2],它必须依赖module map才可以查找模块的内容并提供相关信息,而LLVM目前还不能根据模块的编译信息自动生成module map备用,这导致clangd的使用也受到了一定的限制。相关讨论见clangd discussion #1979。而且目前它还存在一些其他的bug,例如无法为import关键字正确提供基于语义的高亮。

总的来说,STL modules还不太能满足笔者的期望,期待它的后续发展。

References

  1. P2465R3: Standard Library Modules std and std.compat
  2. LLVM doc: Standard C++ Modules
  3. Modules Report for C++20

  1. GCC 15将会有限的支持STL modules ↩︎

  2. 另一个是VSCode C/C++ Tools的LSP server。ccls被看作是clangd的衍生版本,不在此之列。 ↩︎