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
相关产品推荐
相关产品推荐

