概述
我们好久没谈这个了 C++模块 . 我们觉得现在是时候重新审视在MSVC的庇护下发生的事情了。
Visual C++团队一直致力于推动 一致性 到 标准 重点是使用 恢复精力 . 这种复兴的努力使我们有能力实质性地改进我们的模块实现。过去几个月到现在,我们基本上都是透明地完成这项工作。我们自豪地说,工作已经达到了一个点,谈论它将有希望为开发者提供更多的理由来使用MSVC的C++模块。
什么是新的?
- 两阶段名称查找 现在需要使用模块。我们现在以一种比以前的令牌流模型更结构化的形式存储模板,在声明点保存绑定名称等信息。
- 更好的constexpr支持 允许用户在导出的模块中编写更复杂的代码。
- 改进的诊断 为用户在使用MSVC的模块时提供更高的安全性和正确性。
两阶段名称查找支持
这一点最好通过例子来说明。让我们以VS2017 15.7编译器为例,构建一些模块。给定模块m.ixx:
#include <type_traits> export module m; export template <typename T>; struct ptr_holder { static_assert(std::is_same_v<T, std::remove_pointer_t<T>>); };
现在,让我们在一个非常简单的程序中使用这个模块来尝试并触发staticu assert失败,main.cpp:
import m; int main() { ptr_holder<char*> p; }
使用命令行,我们可以这样构建此模块和程序:
cl /experimental:module /std:c++17 /c m.ixxcl /experimental:module /std:c++17 /module:reference m.ifc main.cpp m.obj
但是,您很快就会发现这会导致失败:
m.ixx(7): error C2039: 'is_same_v': is not a member of 'std' predefined C++ types (compiler internal)(238): note: see declaration of 'std' main.cpp(4): note: see reference to class template instantiation 'ptr_holder' being compiled m.ixx(7): error C2065: 'is_same_v': undeclared identifier m.ixx(7): error C2275: 'T': illegal use of this type as an expression m.ixx(7): error C2039: 'remove_pointer_t': is not a member of 'std' predefined C++ types (compiler internal)(238): note: see declaration of 'std' m.ixx(7): error C2061: syntax error: identifier 'remove_pointer_t' m.ixx(7): error C2238: unexpected token(s) preceding ';' main.cpp(35): fatal error C1903: unable to recover from previous error(s); stopping compilation INTERNAL COMPILER ERROR in 'cl.exe' Please choose the Technical Support command on the Visual C++ Help menu, or open the Technical Support help file for more information
它未能编译,只是没有按照我们预期的方式编译。似乎编译器没有处理这个场景。好消息是15.9已经发布了,它带来了一些急需的改进!让我们用15.9编译器构建这个模块和程序:
main.cpp(4): error C2607: static assertion failed main.cpp(4): note: see reference to class template instantiation 'ptr_holder' being compiled
这个!这就是我们要找的!那么这里有什么意义呢?为什么15.9能够处理这种情况,而15.7编译器却以这种方式失败?这一切都归结为模块如何与 两阶段名称查找 .
正如在我们的两阶段名称查找博客中所提到的,模板在编译器中历史上是以令牌流的形式存储的,这些令牌流不会保存有关在解析模板声明期间所看到的标识符的信息。
15.7模块实现没有任何两阶段名称查找的意识,因此使用它编译的模板代码将遇到许多与该博客中描述的问题相同的问题,以及未导出模块代码的按设计查找隐藏特性(在我们的示例中) is_same_v
是非出口申报)。
由于MSVC现在支持两阶段名称查找,我们的模块实现现在能够处理更复杂和正确的代码!
更好的Constexpr支持
CONTXPR是现在对C++非常重要的东西,它与新语言特性相结合的支持会极大地影响该功能的可用性。因此,我们对模块实现的constexpr处理进行了一些重大改进。再一次,让我们从一个具体的例子开始,给出一个模块m.ixx:
export module m; struct internal { int value = 42; }; export { struct S { static constexpr internal value = { }; union U { int a; double b; constexpr U(int a) : a{ a } { } constexpr U(double b) : b{ b } { } }; U u = { 1. }; U u2 = { 1 }; }; constexpr S s; constexpr S a[2] = { {}, {.2, 2} }; }
在程序main.cpp中使用模块:
import m; int main() { static_assert(S::value.value == 42); static_assert(s.u.b == 1. && s.u2.a == 1); static_assert(a[1].u.b == .2 && a[1].u2.a == 2); return s.u2.a + a[1].u2.a; }
您将发现另一个问题:
main.cpp(5): error C3865: '__thiscall': can only be used on native member functions main.cpp(5): error C2028: struct/union member must be inside a struct/union main.cpp(5): fatal error C1903: unable to recover from previous error(s); stopping compilation Internal Compiler Error in cl.exe. You will be prompted to send an error report to Microsoft later. INTERNAL COMPILER ERROR in 'cl.exe' Please choose the Technical Support command on the Visual C++ Help menu, or open the Technical Support help file for more information
好吧,这些都是一些神秘的错误…有人可能会发现,一旦你重写“ constexpr S s;
“以” constexpr S s = { };
“错误消失后,您将面临一个新的运行时失败,因为main的返回值是0,而不是预期的3。一般来说,以前的15.9 constexpr对象和数组是模块中大量错误的来源。像上面提到的那些失败已经完全消失了,部分原因是我们最近对constexpr实现进行了修改。
改进的诊断
MSVC C++模块的实现不仅仅是正确地导出/导入代码,而且还提供了一个安全的、用户友好的体验。
其中一个特性是编译器能够诊断模块接口单元是否被篡改。让我们看一个简单的例子:
C:> cl /experimental:module /std:c++latest /c m.ixxm.ixxC:> echo 1 >> "m.ifc"C:> cl /experimental:module /std:c++latest main.cppmain.cppmain.cpp(1): error C7536: ifc failed integrity checks . Expected SHA2: '66d5c8154df0c71d4cab7665bab4a125c7ce5cb9a401a4d8b461b706ddd771c6'
在此,编译器拒绝尝试使用未通过基本完整性检查的接口文件。这可以保护用户免受试图由MSVC处理的恶意接口文件的攻击。
我们添加的另一个可用性特性是,每当编译器标志与编译模块不同时,就可以向用于导入该模块的编译器标志发出警告。在导入端省略一些命令行开关可能会产生错误的情况:
C:> cl /experimental:module /std:c++17 /MDd /c m.ixxm.ixxC:> cl /experimental:module /std:c++14 /MD main.cppmain.cppmain.cpp(1): warning C5050: Possible incompatible environment while importing module 'm': _DEBUG is defined in module command line and not in current command line main.cpp(1): warning C5050: Possible incompatible environment while importing module 'm': mismatched C++ versions. Current "201402" module version "201703"
编译器告诉我们宏“u DEBUG”是在构建模块时定义的,该宏是在使用/MDd开关时隐式定义的。这个宏的存在会影响像STL这样的库的行为;它甚至可以影响它们的二进制接口(ABI)。另外,这两个组件之间的标准C++版本不一致,因此警告用户。
现在怎么办(行动要求)?
下载Visual Studio 2017 15.9版 今天,尝试用你的项目尝试C++模块。导出您的模板元编程库,并真正隐藏在非导出区域后面的实现细节!不再害怕从接口单元导出constexpr对象!最后,享受使用具有更好诊断支持的模块来创建更友好的用户体验!
下一步是什么…
- 模块标准化正在进行中:当前,MSVC支持当前TS.的所有特性,因为C++模块TS的发展,我们将继续更新我们的实现和特征集来反映新的建议。突破性的变更将按照常规通过发行说明进行记录。
- 吞吐量正在提高:将旧的基础设施与新的基础设施结合使用的一个后果是,新的代码通常依赖于旧的行为。MSVC在这里也不例外。我们不断地更新编译器,使之更快,更少地依赖过时的例程,当这种情况开始发生时,我们的模块实现会加速,这是一个很好的副作用。请继续关注未来的博客。
一如既往,我们欢迎您的反馈。欢迎通过电子邮件发送任何评论 visualcpp@microsoft.com ,通过 推特@visualc ,或Facebook Microsoft Visual Cpp .
如果您在VS 2017遇到MSVC的其他问题,请通过 报告问题 选项,从安装程序或VisualStudioIDE本身。如需建议,请告知我们 开发命令 . 谢谢您!