【问题标题】:Import std lib as modules with clang使用 clang 将 std lib 作为模块导入
【发布时间】:2021-05-30 07:55:09
【问题描述】:

我正在尝试使用 clang 中的模块,并希望将标准库作为模块而不是包含在内。

目前我正在这样做

#include <iostream>
#include <string>

It seems that you in msvc 应该可以导入标准库,例如

import std.core;

然而,当使用 clang 时,这似乎没有实现,或者以其他方式实现。

我的问题是:是否可以像微软建议的那样导入 stl-includes,或者是否可以将标准库包含映射到模块 somhow。

注意:我不能使用 #include &lt;...&gt;#import &lt;...&gt; 的原因是因为其他错误可能会产生自己的问题。因此,如果可能的话,我认为现在获得import std.core 或类似的方法是可行的。

ModernesCpp 也提到了 std.core。

【问题讨论】:

  • "这个好像没有实现" 当然不是; Clang 通常不实现随机的 Microsoft 扩展。而且你真的不应该使用它们。
  • 我不能使用#include <...> 或#import <...> 的原因是因为其他错误可能会引起自己的问题。 一个很奇怪原因。您的代码有错误,您将使用 import 解决它们。
  • @NicolBolas 你想误解这个问题吗?据我了解,应该有某种方法可以将 stl-includes 映射到 c++20 导入,但是我再也找不到该页面了,我再把它变红了。
  • 模块化标准库在 c++23 的优先级列表中

标签: c++ clang++ c++20 c++-modules


【解决方案1】:

C++20 标准不包括 C++ 标准库的模块定义。 Visual Studio 确实(不幸的是),而且很多糟糕的网站都会表现得像这样是标准的。但事实并非如此;这只是微软的事情。

如果您想通过跨平台的模块包含 C++ 标准库,则必须使用 import &lt;header-name&gt; 语法或编写自己的标准库模块来导入标头并导出特定的 C++ 声明。

【讨论】:

  • 这可能是对的。你有什么好主意如何从包含的头文件中导出符号,你也许可以回答一个似乎没有人破解的老问题:stackoverflow.com/questions/66239902/…
  • 至少在 Windows 的 CLang 下一个简单程序 import &lt;iostream&gt;; int main() {} 无法编译,它显示为 use2.cpp:1:8: error: header file &lt;iostream&gt; (aka 'd:\bin2\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\include\iostream') cannot be imported because it is not known to be a header unit。因此,至少在使用import &lt;header-name&gt;; 赢得您的解决方案时不起作用。只有我的解决方案 described here 适用于 Win 的 CLang。
  • @Arty:“只有我这里描述的解决方案适用于 Win 的 CLang。”这个问题是关于 C++20 特性的。 C++20 说 import &lt;iostream&gt;; 有效。如果它在特定的编译器上不起作用,那是 compiler 未能正确实现 C++20 的错误。 Clang 的 C++20 模块支持功能不完整。
  • @NicolBolas 感谢您的回复!我虽然OP的问题很实用,意思是如何在CLang上解决它(标题和标签中有CLang),也谈论MSVC意味着OP在Win上。我不认为它应该如何在所有编译器上使用标准 C++20 是理论上的。
  • @Arty 是的。当我问这个问题时,我认为import std.xxx; 是要走的路,然后我意识到import &lt;xxx&gt;; 确实是正确的语法。
【解决方案2】:

我解决了你的任务。以下是执行此操作的说明。我在 Win 10 64 位上使用 LLVM 12.0 的当前版本(取自 here)中的 CLang 执行此操作,我还安装了 MSVC 2019 v16.9.4 社区(取自 here)。

首先创建以下文件:

module.modulemap

module std_mod {
    requires cplusplus17
    header "std_mod.hpp"
    export *
}

std_mod.hpp

#include <iostream>
#include <map>
#include <set>
#include <vector>

使用.cpp

import std_mod;

int main() {
    std::cout << "Hello, World!" << std::endl;
}

在上面的文件std_mod.hpp 中,您可以放置​​您需要的任何标准头文件。您应该将您在所有项目中使用的所有可能的 STD 标头都放入,以便能够在任何地方共享相同的预编译 STD 模块。

然后执行命令:

clang++ -### use.cpp -c -std=c++20 -m64 -g -O3 >use.txt 2>&1

在这里,您可以使用项目所需的任何选项,而不是 -std=c++20 -m64 -g -O3。每个预编译模块都应具有与其他 .cpp 文件相同的编译选项,以便能够链接到最终二进制文件中。

上面的命令将产生use.txt,其中包含您需要复制的选项。在此选项中,您应该删除 -emit-obj 选项、-o 选项(及其后的路径),同时删除 use.cpp。然后添加到这个命令选项字符串module.modulemap -o std_mod.pcm -emit-module -fmodules -fmodule-name=std_mod。在我的系统上,我得到了以下结果命令:

"D:\\bin\\llvm\\bin\\clang++.exe" "-cc1" module.modulemap -o std_mod.pcm -emit-module -fmodules -fmodule-name=std_mod  "-triple" "x86_64-pc-windows-msvc19.28.29914" "-mincremental-linker-compatible" "--mrelax-relocations" "-disable-free" "-disable-llvm-verifier" "-discard-value-names" "-main-file-name" "use.cpp" "-mrelocation-model" "pic" "-pic-level" "2" "-mframe-pointer=none" "-fmath-errno" "-fno-rounding-math" "-mconstructor-aliases" "-munwind-tables" "-target-cpu" "x86-64" "-tune-cpu" "generic" "-gno-column-info" "-gcodeview" "-debug-info-kind=limited" "-resource-dir" "D:\\bin\\llvm\\lib\\clang\\12.0.0" "-internal-isystem" "D:\\bin\\llvm\\lib\\clang\\12.0.0\\include" "-internal-isystem" "d:\\bin2\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.28.29910\\include" "-internal-isystem" "d:\\bin2\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.28.29910\\atlmfc\\include" "-internal-isystem" "D:\\Windows Kits\\10\\Include\\10.0.19041.0\\ucrt" "-internal-isystem" "D:\\Windows Kits\\10\\include\\10.0.19041.0\\shared" "-internal-isystem" "D:\\Windows Kits\\10\\include\\10.0.19041.0\\um" "-internal-isystem" "D:\\Windows Kits\\10\\include\\10.0.19041.0\\winrt" "-O3" "-std=c++20" "-fdeprecated-macro" "-fdebug-compilation-dir" "D:\\t\\t4" "-ferror-limit" "19" "-fno-use-cxa-atexit" "-fms-extensions" "-fms-compatibility" "-fms-compatibility-version=19.28.29914" "-fdelayed-template-parsing" "-fno-implicit-modules" "-fcxx-exceptions" "-fexceptions" "-vectorize-loops" "-vectorize-slp" "-faddrsig" "-x" "c++"

如您所见,此命令包含包含的完整路径,它们是必需的。执行上面的命令,它会产生std_mod.pcm,你可以在你的项目中使用相同的编译选项。

为什么需要上面的长命令?因为只有通过-cc1 命令才能使用.modulemap 文件,该命令执行低级CLang 前端而不是简化的CLang 驱动程序(驱动程序没有-cc1 选项)。这个低级的前端可以做很多驱动程序做不到的技巧。

现在你可以通过下一个命令编译你的最终程序use.cpp,它执行import std_mod;

clang++ use.cpp -o use.exe -std=c++20 -m64 -g -O3 -fmodule-file=std_mod.pcm

看到我添加了-fmodule-file=std_mod.pcm - 每个导入的模块都需要这样的选项。作为替代方案,您可以使用 -fprebuilt-module-path=&lt;directory&gt; 指定搜索所有预构建模块的位置。

不久前,我还创建了关于如何在 CLang 中使用标头制作模块的问答 here

有关模块的更多说明,请参阅 CLang 的 Modules DocCommandLine Doc

附言。为什么我在上面实施了相当长的解决方案?因为至少在 Windows 的 CLang 下一个简单的程序

import <iostream>;
int main() {}

无法编译,它显示use.cpp:1:8: error: header file &lt;iostream&gt; (aka 'd:\bin2\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\include\iostream') cannot be imported because it is not known to be a header unit。所以至少在 Win 上需要一个特殊的解决方案,import &lt;header-name&gt;; 的解决方案在这里不起作用。

所有通过import &lt;header&gt;;import "header"; 语法导入的头文件都应该有特殊的已编译头文件单元模块放置在特殊文件夹中以便能够使用。并且在 Win STD 标头上没有相应的已编译标头单元模块。同样在花了很多时间之后,我没有在 CLang 中找到如何在 Win 上创建这些所谓的标头单元的方法。只有上面的解决方案解决了我将标头作为模块导入的任务。

【讨论】:

  • 在@NicolBolas 回复之后,我开始意识到我们真正想要的是让import &lt;header&gt;; 语法起作用。在你写的时候,有点难过创建编译器头单元是如此困难。我认为很高兴能够参考您在实践中如何将标头作为模块导入,不过非常感谢您分享您的工作。
  • 我没有写在我的答案中,但是我在linux上运行clang。但我猜语法有些相同。
  • 如果你将module.modulemap重命名为module.cc,那么这个命令将生成std_mod.pcm:clang -Xclang -emit-module -std=c++20 -c module.cc -o std_mod.pcm -fmodules -fmodule-name=std_mod(在OS X上测试,希望也能在Windows上运行)
  • @jjrv 感谢您在 OS X 上试用。不知道 .modulemap 可以成功编译为 .cc,似乎是完全不同的语言。如果您运行相同的命令但不重命名为.cc,会发生什么?编译不出错?
  • @arty 它没有编译为.cc,但如果扩展名是.modulemap,那么clang 前端将尝试将其提供给链接器,该链接器抱怨不支持的“二进制”格式。 cc1 后端显然会注意到它是一个模块映射,即使扩展名是.cc。而-emit-module 可以通过-Xclang 传递给cc1。另外,至少在我的机器上,你实际上可以省略-fmodules
【解决方案3】:

我找到了我的想法,llvms documentation。有一段说明

“例如,C 标准库的模块映射文件可能看起来有点像这样:”

module std [system] [extern_c] {
  module assert {
    textual header "assert.h"
    header "bits/assert-decls.h"
    export *
  }

  module complex {
    header "complex.h"
    export *
  }

  module ctype {
    header "ctype.h"
    export *
  }

  module errno {
    header "errno.h"
    header "sys/errno.h"
    export *
  }

  module fenv {
    header "fenv.h"
    export *
  }

  // ...more headers follow...
}

您似乎随后将此文件命名为something.modulemap 并将其发送给编译器。快速谷歌搜索没有找到任何类似的 msvc 解决方案(除了前面讨论的 std.io)。

我还没有尝试过,我想我会接受我自己的答案,或者如果其他人想出更好的答案。

【讨论】:

  • 您所引用的站点是一种较旧形式的模块,基于较旧的、未被接受的提案。我不知道它是否代表 Clang 中的功能,但它肯定不代表 C++20 模块。
  • 好的,很高兴知道,但问题是关于 clang。遗憾的是没有标准的方法来做到这一点:/
  • "甚至不知道它当前是否是 clang 的一部分?" 这有关系吗?您的问题与 C++20 的模块功能有关,而您引用的文档与此无关。它在 Clang 中是否仍然可用是无关紧要的; C++20 模块不能那样工作
  • @NicolBolas 好吧,如果问题的主题是“将 std lib 作为带有 clang 的模块导入”,那么可以假设问题是关于 clang,而不是一般的 c++。
  • 如果是这种情况,您不应该使用 C++20 和 C++ 模块对其进行标记。您要么询问 C++20 模块功能,因为它适用于某些版本的 Clang,要么询问 Clang。
猜你喜欢
  • 2019-09-24
  • 2012-11-12
  • 1970-01-01
  • 2018-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-27
  • 2020-09-07
相关资源
最近更新 更多