启用/permissive-时MSVC拒绝该代码是否合规?
测试代码
#include <array> #include <cstdint> #include <optional> namespace foo { template <std::size_t> using size_t = int; } using namespace foo; struct Foo { Foo() : a{} {} std::array<std::optional<int32_t>, 2> a; }; int main() {}
编译现象与错误信息
当使用/permissive-标志编译上述代码时,MSVC会抛出符号歧义错误,错误信息如下:
predefined C++ types (compiler internal)(421): error C2872: 'size_t': ambiguous symbol <source>: note: could be 'unsigned __int64 size_t' <source>(7): note: or 'foo::size_t = int' predefined C++ types (compiler internal)(421): note: the template instantiation context (the oldest one first) is <source>(13): note: see reference to function template instantiation 'void __builtin_array_init_helper<_Ty>(_T *,size_t) noexcept' being compiled with [ _Ty=std::optional<int32_t>, _T=std::optional<int32_t> ] Compiler returned: 2
而未启用/permissive-的MSVC,以及无论是否启用-fno-permissive的Clang和GCC,均能正常编译该代码。
可修复错误的修改方案
单独做出以下任一修改,均可让启用/permissive-的MSVC编译通过:
- 移除
a的std::array层; - 移除
a的std::optional层; - 移除手动定义的默认构造函数;
- 仅移除默认构造函数中的初始化列表;
- 移除
using namespace foo;指令。
错误原因分析
从C++标准和编译器实现角度看,MSVC的行为不符合规范,问题出在以下几点:
1. 标准库内部符号的查找逻辑错误
编译器内部的__builtin_array_init_helper是标准库实现的辅助函数,其参数中的size_t应优先解析为std::size_t(标准定义的类型)。根据C++标准,标准库代码的上下文属于std命名空间,未限定的size_t在标准库内部函数中应绑定到标准定义,而using namespace foo;是用户代码的命名空间引入,不应渗透到标准库内部的符号查找中。MSVC错误地将用户命名空间的foo::size_t纳入了该辅助函数的符号候选集,导致歧义。
2. 模板实例化的上下文隔离失效
C++标准区分模板的定义上下文和实例化上下文:标准库模板(包括内部辅助模板)的定义上下文在std命名空间内,因此实例化时应优先使用定义上下文的符号,而非用户代码实例化上下文的同名符号。MSVC在实例化__builtin_array_init_helper时,错误地将用户代码的作用域(通过using namespace foo;引入的符号)带入了标准库模板的解析过程,违反了上下文隔离规则。
3. 初始化逻辑的作用域处理缺陷
只有当手动定义默认构造函数并使用a{}初始化时才会触发错误,说明MSVC在处理聚合初始化的内部辅助逻辑中,没有正确隔离用户命名空间的符号引入。而使用合成默认构造函数时不会触发该路径,进一步证明这是MSVC初始化辅助代码的实现缺陷。
内容的提问来源于stack exchange,提问作者Enlico

