目录

C++23:标准库模块(一)

系列 - C++ Features

随着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工具的配置。

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

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

测试环境:

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

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

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查看结果。

本文通过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语句所替换。而且由于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还不太能满足笔者的期望,期待它的后续发展。

  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的衍生版本,不在此之列。 ↩︎