非类型参数包笛卡尔积代码C++17编译失败原因咨询
非类型参数包实现笛卡尔积的编译问题解析
问题场景
用非类型参数包实现笛卡尔积的代码,可在GCC、MSVC的C20模式下编译,但C17模式编译失败,且完全无法在Clang中编译。代码未显式使用C++20特性,却出现跨版本、跨编译器的差异,编译错误仅模糊提示“template argument deduction/substitution failed”,无更多细节。
最简代码片段
#include <iostream> #include <utility> template<auto ...values> struct ValueParameterPack {}; template<std::size_t I, typename T, T ...values> constexpr T get(ValueParameterPack<values...>) { constexpr T value_array[] = {values...}; return value_array[I]; } template<std::size_t I, typename T> constexpr auto get_v = get<I>(T{}); template<auto ...values1, auto ...values2, std::size_t ...Is> auto cartesian_product(ValueParameterPack<values1...>, ValueParameterPack<values2...>, std::index_sequence<Is...>) -> ValueParameterPack< std::make_pair( get_v<Is / sizeof...(values2), ValueParameterPack<values1...>>, get_v<Is % sizeof...(values2), ValueParameterPack<values2...>> )... >; template<auto ...values1, auto ...values2> auto cartesian_product(ValueParameterPack<values1...>, ValueParameterPack<values2...>) -> decltype(cartesian_product( ValueParameterPack<values1...>{}, ValueParameterPack<values2...>{}, std::make_index_sequence<sizeof...(values1) * sizeof...(values2)>() )); template<typename ValueParameterPack1, typename ValueParameterPack2> using CartesianProduct = decltype(cartesian_product(ValueParameterPack1{}, ValueParameterPack2{})); int main() { using T = CartesianProduct<ValueParameterPack<1, 2>, ValueParameterPack<3, 4>>; }
编译失败原因解析
1. C17与C20的模板推导差异
核心问题出在第二个cartesian_product重载的返回类型推导逻辑:
template<auto ...values1, auto ...values2> auto cartesian_product(...) -> decltype(cartesian_product(..., std::make_index_sequence<sizeof...(values1)*sizeof...(values2)>()));
在C17中,函数模板返回类型的decltype表达式无法反向推导模板参数。这里返回类型调用了第一个重载,但编译器需要先确定values1、values2才能生成index_sequence,而这两个参数包又需要从函数参数推导——形成了循环依赖,C17编译器无法处理这种推导逻辑。
C20通过P0859R0提案放宽了规则,允许在返回类型的decltype中使用当前函数模板的参数,因此GCC和MSVC的C20模式可以正常编译。
2. 模糊错误信息的含义
“template argument deduction/substitution failed”本质是编译器在推导模板参数时,无法找到符合要求的重载匹配:
- 实例化
CartesianProduct时,会触发第二个cartesian_product重载的调用 - 编译器需要推导
values1和values2,但返回类型的decltype调用依赖这两个参数包的大小,导致推导无法完成 - 多数编译器在处理这类复杂模板推导失败时,只会给出通用错误提示,不会详细说明循环依赖的具体问题
3. Clang编译失败的原因
Clang对模板推导规则的执行更严格:
- 即使在C++20模式下,Clang也可能拒绝这种返回类型依赖模板参数的推导逻辑
- 另外,
get_v的定义中,Clang无法从T{}(ValueParameterPack实例)中正确推导get函数的T模板参数,而GCC和MSVC做了非标准的宽松推导,因此能通过编译
兼容C++17的修复方案
改用类模板特化实现笛卡尔积,避免函数模板的返回类型循环推导:
#include <iostream> #include <utility> template<auto ...values> struct ValueParameterPack {}; template<std::size_t I, typename T, T ...values> constexpr T get(ValueParameterPack<values...>) { constexpr T value_array[] = {values...}; return value_array[I]; } template<std::size_t I, typename T> constexpr auto get_v = get<I>(T{}); // 类模板实现笛卡尔积逻辑 template<typename Pack1, typename Pack2> struct CartesianProductImpl; template<auto... V1, auto... V2> struct CartesianProductImpl<ValueParameterPack<V1...>, ValueParameterPack<V2...>> { private: template<std::size_t... Is> static auto generate(std::index_sequence<Is...>) { return ValueParameterPack< std::make_pair( get_v<Is / sizeof...(V2), ValueParameterPack<V1...>>, get_v<Is % sizeof...(V2), ValueParameterPack<V2...>> )... >{}; } public: using type = decltype(generate(std::make_index_sequence<sizeof...(V1)*sizeof...(V2)>())); }; template<typename Pack1, typename Pack2> using CartesianProduct = typename CartesianProductImpl<Pack1, Pack2>::type; int main() { using T = CartesianProduct<ValueParameterPack<1, 2>, ValueParameterPack<3, 4>>; }
类模板特化可以提前解析ValueParameterPack的参数包,避免函数模板推导中的循环依赖,同时兼容C++17和主流编译器。
内容的提问来源于stack exchange,提问作者Nelfeal
相关产品推荐
相关产品推荐

