MSVC下使用自定义constexpr函数实现SFINAE编译错误解决方案问询
问题背景
我编写了一段使用constexpr实现的简易SFINAE代码,该代码可在*g++*下正常编译,但在Microsoft Visual C++下编译失败。该问题仅在使用自定义constexpr函数时出现,直接使用std::is_integral_v<T>作为SFINAE判断条件则编译正常。
测试代码
// 测试代码 #include <iostream> #include <type_traits> template<typename T> constexpr inline bool is_integral() { return std::is_integral_v<T>; } template<typename T> constexpr inline bool is_floating_point() { return std::is_floating_point_v<T>; } struct tester { template<typename T> std::enable_if_t<is_integral<T>(), void> operator()(int* p){ // 整数类型版本 std::cout << "integral\n"; } template<typename T> std::enable_if_t<is_floating_point<T>(), void> operator()(int* p){ // 浮点类型版本 std::cout << "floating\n"; } template <typename T, typename... Args> std::enable_if_t<!is_integral<T>() && !is_floating_point<T>(), void> operator()(Args&&...) { std::cerr << "not supported.\n"; } }; enum class type { INT, FLOAT, STRING}; void type_dispatcher(type tp) { tester t; switch(tp) { case type::INT: t.operator()<int>(nullptr); break; case type::FLOAT: t.operator()<float>(nullptr); break; default: std::cout << "Unsupported\n"; } } int main() { type t1{type::INT}, t2{type::FLOAT}, t3{type::STRING}; type_dispatcher(t1); type_dispatcher(t2); type_dispatcher(t3); return 0; }
编译错误信息
编译命令:cl.exe /std:c++17 ..\sfinae_test.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30136 for x64 Copyright (C) Microsoft Corporation. All rights reserved. sfinae_test.cpp ..\sfinae_test.cpp(21): error C2535: 'enable_if<0,void>::type tester::operator ()(int *)': 成员函数已定义或声明 ..\sfinae_test.cpp(16): note: 参见“tester::operator ()”的声明 ..\sfinae_test.cpp(44): error C2672: 'tester::operator ()': 未找到匹配的重载函数 ..\sfinae_test.cpp(44): error C2893: 未能将函数模板“enable_if<0,void>::type tester::operator ()(int *)”专用化 ..\sfinae_test.cpp(16): note: 参见“tester::operator ()”的声明 ..\sfinae_test.cpp(44): note: 以下是模板参数: ..\sfinae_test.cpp(44): note: 'T=float'
可行规避方案
- 方案1:将自定义
constexpr函数替换为模板变量
MSVC对函数形式的constexpr返回值作为SFINAE条件的解析存在已知缺陷,直接改用模板变量可完全规避该问题。修改代码如下:
// 替换原来的函数定义 template<typename T> constexpr inline bool is_integral_v = std::is_integral_v<T>; template<typename T> constexpr inline bool is_floating_point_v = std::is_floating_point_v<T>; // SFINAE条件改为使用变量,去掉括号调用 template<typename T> std::enable_if_t<is_integral_v<T>, void> operator()(int* p){ std::cout << "integral\n"; } template<typename T> std::enable_if_t<is_floating_point_v<T>, void> operator()(int* p){ std::cout << "floating\n"; }
- 方案2:为模板重载增加区分性默认参数
如果必须保留函数形式的constexpr判断,可以给每个模板重载添加不同的默认非类型参数,强制MSVC识别为不同的模板签名:
template<typename T, int = 0> std::enable_if_t<is_integral<T>(), void> operator()(int* p){ std::cout << "integral\n"; } template<typename T, int = 1> std::enable_if_t<is_floating_point<T>(), void> operator()(int* p){ std::cout << "floating\n"; }
两个不同的默认模板参数会避免MSVC误判为重定义。
- 方案3:改用标签分发实现重载逻辑
完全避开SFINAE场景下的constexpr函数调用,改用标签分发实现同样的分支逻辑,兼容性最好:
struct tester { template<typename T> void operator()(int* p) { dispatch<T>(p, std::is_integral<T>{}, std::is_floating_point<T>{}); } template <typename T, typename... Args> void operator()(Args&&...) { std::cerr << "not supported.\n"; } private: template<typename T> void dispatch(int* p, std::true_type /*is_integral*/, std::false_type) { std::cout << "integral\n"; } template<typename T> void dispatch(int* p, std::false_type, std::true_type /*is_floating_point*/) { std::cout << "floating\n"; } };
内容的提问来源于stack exchange,提问作者lxkarthi
相关产品推荐
相关产品推荐

