MSVC无法推导模板参数:C++17代码兼容Clang/G++却报错
C++17模板参数推导在MSVC中的问题与解决方法
问题背景
以下C++17代码可在Clang、GCC中正常编译,但在MSVC 19.37.32822版本中无法推导模板参数:
#include <type_traits> struct Prop { constexpr Prop() {}; }; constexpr const Prop i; struct Klass { template <auto Klass::*MEMBER, const Prop &P, std::enable_if_t<std::is_member_pointer_v<decltype(MEMBER)>, bool> = true> void def() {} void fn(); int var; }; void Klass::fn() {} int main() { Klass k; k.def<&Klass::fn, i>(); k.def<&Klass::var, i>(); }
测试发现,仅移除std::enable_if或仅移除第二个非类型引用模板参数即可修复MSVC的编译错误,推测这是MSVC的编译器bug。
代码合法性分析
这段代码完全符合C++17标准,不存在语法问题:
- C++17允许
auto Klass::*MEMBER这种非类型模板参数写法,用于匹配类的成员指针; std::enable_if_t作为模板默认参数的SFINAE用法是标准许可的;- 非类型模板参数使用
const Prop&绑定全局常量i,符合C++17对非类型模板参数引用的要求。
Clang和GCC的正常编译行为也能佐证代码的合规性,因此这确实是MSVC的实现缺陷。
保留std::enable_if的兼容方案
方案1:将enable_if移至函数返回值
把std::enable_if_t从模板参数列表转移到函数返回值位置,调整后代码如下:
struct Klass { template <auto Klass::*MEMBER, const Prop &P> std::enable_if_t<std::is_member_pointer_v<decltype(MEMBER)>> def() {} void fn(); int var; };
这种写法同样符合SFINAE规则,且MSVC能正确处理模板参数推导。
方案2:将enable_if改为类型模板参数
把enable_if的结果作为类型模板参数而非非类型参数,修改模板参数列表:
struct Klass { template <auto Klass::*MEMBER, const Prop &P, typename = std::enable_if_t<std::is_member_pointer_v<decltype(MEMBER)>>> void def() {} void fn(); int var; };
这种写法避免了MSVC在处理非类型模板参数与enable_if组合时的推导bug,同时保留了SFINAE的筛选逻辑。
方案3:调整enable_if的非类型参数类型
将enable_if的第二个模板参数从bool改为int,默认值设为0,有时能绕过MSVC的推导逻辑缺陷:
struct Klass { template <auto Klass::*MEMBER, const Prop &P, std::enable_if_t<std::is_member_pointer_v<decltype(MEMBER)>, int> = 0> void def() {} void fn(); int var; };
内容的提问来源于stack exchange,提问作者Criminal_Affair_At_SO
相关产品推荐
相关产品推荐

