MinGW64与MSVC函数决议结果差异的C++标准判定及疑问
第一个版本代码编译器差异的标准符合性判断
对应代码:
template <typename T> int get_num(const T&) { return 42; } struct Foo { int i = get_num(*this); }; int get_num(const Foo&) { return 23; } int main() { std::cout << Foo().i << std::endl; // MinGW64 - 42, MSVC - 23 return 0; }
- 结论:MinGW64(GCC)的实现符合C++标准,MSVC的实现不符合标准
- 原因:
- 针对Foo类内默认成员初始化器的
get_num(*this)调用,C++标准要求非限定名查找仅考虑调用点之前已经声明的实体。该调用点位于Foo类定义内部,此时全局作用域仅存在模板版本的get_num,非模板重载的声明在Foo类定义结束之后,调用点不可见。 - ADL(参数依赖查找)同样不会找到调用点之后才声明的普通非模板函数,因此标准要求下只能选择模板版本的
get_num,和MinGW的行为一致。 - MSVC的异常行为源于其传统的非标准查找实现:它会将类内初始化器的名字查找延迟到整个类定义完成、甚至翻译单元解析到末尾后执行,因此能看到后续声明的非模板重载,不符合标准规则。
- 针对Foo类内默认成员初始化器的
第二个版本代码输出差异的原因
对应代码:
template <typename T> int get_num(const T& t) { std::cout << "Template version called" << std::endl; return get_num(t); } struct Foo { int i = get_num(*this); }; int get_num(const Foo&) { std::cout << "Non-Template version called" << std::endl; return 23; }
- MinGW的符合标准的行为逻辑:
get_num(*this)调用点仅可见模板版本,因此实例化get_num<Foo>,首先打印模板版本的日志。- 模板内部的
get_num(t)是依赖模板参数的调用,会触发两阶段查找的第二阶段:在模板实例化点执行ADL查找,此时非模板版本的get_num(const Foo&)已经声明可见,因此调用非模板版本,打印对应日志并返回23,不会出现无限递归。
- MSVC的非标准行为逻辑:
- 还是因为其延迟查找的实现,在处理
get_num(*this)时已经能看到后续声明的非模板重载,非模板重载是比模板更匹配的候选,因此直接调用非模板版本,不会实例化模板,仅打印非模板版本的日志。
- 还是因为其延迟查找的实现,在处理
- 该差异确实和MSVC的ADL以及两阶段查找的非标准实现有关,在MSVC中开启
/permissive-开关启用标准兼容模式后,也会得到和MinGW一致的输出。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

