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

C++简化模板接口移除冗余类型名实现类模板参数自动推导

问题说明

C++原生支持函数模板实参推导,但标准明确规定虚函数不能是模板,因此原本需要定义在虚函数上的模板参数只能上移到类模板层级。但类模板参数无法直接通过构造函数实参、成员变量完成全量推导,使用时必须手动填写大量冗余模板参数,导致T、T1接口易用性很差。

已知可用于模板参数推导的固定约束:

  • sizeof...(V) == N
  • 所有V的类型固定为const char*
  • 模板参数U可完全通过T1构造函数传入的实参推导
  • 模板参数包V...可完全通过T1构造函数传入的实参推导
  • 模板参数N可由参数包V...的长度直接推导得到

问题原始代码

#include <cstddef>
#include <utility>

template <size_t N, typename... V>
struct T {
    int test(size_t n, const char** ss) {
        if (N > n)
            return 1;
        return []<size_t... I>(const char* ss[N], std::index_sequence<I...>) {
            return test_impl(ss[I]...);
        }(ss, std::make_index_sequence<N>{});
    }

    // 理想方案是将此处改为模板函数,直接解决问题
    // 即把结构体层级的typename... V移动到该函数声明上
    virtual int test_impl(V...) = 0;
};

template <typename U, size_t N, typename... V>
struct T1 : T<N, V...> {
    U value;
    int (*tester)(U*, V...);

    int test_impl(V... v) {
        return tester(&value, v...);
    }

    // 存在虚函数,不是聚合类型,需要自定义构造函数
    T1(U value, int (*tester)(U*, V...))
        : value{value}, tester{tester}
        {};
};

int test1(int* v, const char* s1, const char* s2) {
    return 1;
}

template <typename... V>
int test2(int* v, V... ss) {
    return 1;
}

int main() {
    // 此处的2和重复的const char*均为冗余信息
    T1 a = T1<int, 2, const char*, const char*>(10, test1);
    T1 b = T1<int, 2, const char*, const char*>(10, test2);

    // 现有信息足够完成全量模板参数推导
    T1 c = T1(10, test1);

    // 现有信息足够完成全量模板参数推导
    T1 d = T1<..., 2, ...>(10, test2);
}
修复方案

使用C++17引入的*类模板实参推导(CTAD)*特性,自定义推导指引,从传入构造函数的函数指针签名中自动拆分出U、参数包V,再通过参数包长度自动计算N,完全消除冗余模板参数的手动填写。

修复后完整代码

#include <cstddef>
#include <utility>

template <size_t N, typename... V>
struct T {
    int test(size_t n, const char** ss) {
        if (N > n)
            return 1;
        return []<size_t... I>(const char** ss, std::index_sequence<I...>) {
            return test_impl(ss[I]...);
        }(ss, std::make_index_sequence<N>{});
    }

    virtual int test_impl(V...) = 0;
};

template <typename U, size_t N, typename... V>
struct T1 : T<N, V...> {
    U value;
    int (*tester)(U*, V...);

    int test_impl(V... v) override {
        return tester(&value, v...);
    }

    T1(U value, int (*tester)(U*, V...))
        : value{value}, tester{tester}
        {};
};

// 自定义CTAD推导指引,自动从构造函数参数拆分模板实参
template <typename U, typename... V>
T1(U, int(*)(U*, V...)) -> T1<U, sizeof...(V), V...>;

int test1(int* v, const char* s1, const char* s2) {
    return 1;
}

template <typename... V>
int test2(int* v, V... ss) {
    return 1;
}

int main() {
    // 自动推导为T1<int, 2, const char*, const char*>,无冗余参数
    T1 c(10, test1);
    // 模板函数需显式指定模板参数列表,自动推导N=2
    T1 d(10, test2<const char*, const char*>);

    // 功能验证
    const char* args[] = {"foo", "bar"};
    c.test(2, args);
    d.test(2, args);
    return 0;
}

注意事项

  • 该方案要求编译环境支持C++17及以上标准,这是CTAD特性的最低版本要求
  • 对于test2这类函数模板,直接传入函数名无法确定具体实例地址,必须显式指定模板参数列表后才能传入构造函数完成推导,这是C++语法的固有规则,无法省略
  • 所有V类型为const char*的约束会在编译期自动校验,如果传入的测试函数参数类型不匹配,调用test方法时会直接触发编译错误,符合约束要求

内容的提问来源于stack exchange,提问作者user19087

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 02:31:06