You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非类型参数包笛卡尔积代码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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:15:31