将可变参数类型展开到另一类型的跨编译器兼容实现问题
问题解答
首先明确:这确实是MSVC的一个已确认bug,你联系团队后他们正在处理是正确的——MSVC在处理void_t展开可变参数的SFINAE场景时存在逻辑缺陷,没有正确检查每个ReturnsType特化的有效性,而GCC和Clang的实现符合C++标准要求。
兼容所有编译器的替代实现
你可以绕过void_t的问题,改用两种更可靠的方式实现AllReturnsType:
方式1:C++17及以上推荐——折叠表达式
利用C++17引入的折叠表达式直接对每个函数的返回类型做逻辑与判断,代码简洁且兼容性好:
#include <type_traits> #include <utility> template<typename TReturn, typename TFunction> using ReturnsType = std::enable_if_t<std::is_same_v<decltype(std::declval<TFunction>()()), TReturn>>; // 兼容所有主流编译器的AllReturnsType template<typename TReturn, typename... TFunctions> using AllReturnsType = std::enable_if_t<(std::is_same_v<decltype(std::declval<TFunctions>()()), TReturn> && ...)>;
方式2:C11/C14兼容——使用conjunction trait
如果需要兼容更早的C标准,可以借助std::conjunction(C14引入,若编译器不支持可自行实现类似的逻辑与模板):
#include <type_traits> #include <utility> template<typename TReturn, typename TFunction> using ReturnsType = std::enable_if_t<std::is_same<decltype(std::declval<TFunction>()()), TReturn>::value>; // 辅助结构体,递归判断所有函数返回类型 template<typename TReturn, typename... TFunctions> struct AllReturnsTypeHelper : std::conjunction<std::is_same<decltype(std::declval<TFunctions>()()), TReturn>...> {}; // 对外暴露的别名模板 template<typename TReturn, typename... TFunctions> using AllReturnsType = std::enable_if_t<AllReturnsTypeHelper<TReturn, TFunctions...>::value>;
测试验证
你可以用以下代码验证逻辑正确性:
// 测试函数 int foo() { return 42; } int bar() { return 123; } float baz() { return 3.14f; } // 仅当所有传入函数都返回int时才匹配的模板 template<typename... Fns> void check_all_int(AllReturnsType<int, Fns...>*) {} int main() { // 编译通过:两个函数都返回int check_all_int<int(*)(), int(*)()>(nullptr); // 编译失败:baz返回float,不符合要求(注释掉可看到错误) // check_all_int<int(*)(), float(*)()>(nullptr); return 0; }
关于MSVC的bug
正如你所说,这个问题已经被MSVC团队确认并进入处理流程,后续的编译器更新应该会修复void_t在可变参数展开场景下的SFINAE问题。在此之前,上述替代实现可以保证代码在VC++、GCC、Clang中都正常工作。
内容的提问来源于stack exchange,提问作者Ivan Sanz Carasa
相关产品推荐
相关产品推荐

