如何正确推导C++模板参数?类型与非类型混合特化问题求解
混合类型/非类型模板参数的推导问题
当模板参数为类型与非类型模板参数的组合时,模板特化后编译器无法完成正确的参数推导。示例代码如下:
#include <stdio.h> struct sa {}; struct sb {}; /// tmpl_vs has sevaral different classes as arguments, just use 1 here as example. template <class a_type, int VS> struct tmpl_vs { static_assert(sizeof(a_type) == 0, "Unexpected."); }; template <int vs> struct tmpl_vs<sa, vs> { void func() { printf("tmpl_vs<sa,vs>\n"); } }; template <int vs> struct tmpl_vs<sb, vs> { void func() { printf("tmpl_vs<sb,vs>\n"); } }; template<class... Args> void tmpl_func(tmpl_vs<Args...> &) { printf("1\n"); } template<class... Args, int... I> void tmpl_func(tmpl_vs<Args..., I...> &) { printf("2\n"); } int main(int argc, char* argvp[]) { tmpl_vs<sa, 1> a; tmpl_func(a); return 0; }
上述代码中,main函数定义了tmpl_vs<sa, 1>类型的对象a,调用tmpl_func(a)时,编译器会匹配第一个重载(输出"1"),而非预期的第二个重载(输出"2")。
问题原因
编译器在处理第二个重载tmpl_func(tmpl_vs<Args..., I...> &)时,无法将tmpl_vs<sa, 1>的模板参数拆分为Args...(类型参数包)和I...(非类型参数包)。因为Args...属于非推导上下文,编译器没有明确规则来划分哪些参数属于类型包、哪些属于非类型包,因此只能退而求其次匹配更通用的第一个重载。
解决方法
核心思路是消除推导歧义,明确告诉编译器如何拆分tmpl_vs的模板参数,以下是两种可行方案:
方案一:使用模板元编程提取参数
通过辅助结构体提取tmpl_vs的类型和非类型参数,再编写明确的重载:
#include <stdio.h> struct sa {}; struct sb {}; template <class a_type, int VS> struct tmpl_vs { static_assert(sizeof(a_type) == 0, "Unexpected."); }; template <int vs> struct tmpl_vs<sa, vs> { void func() { printf("tmpl_vs<sa,vs>\n"); } }; template <int vs> struct tmpl_vs<sb, vs> { void func() { printf("tmpl_vs<sb,vs>\n"); } }; // 辅助模板:提取tmpl_vs的类型和非类型参数 template <typename T> struct tmpl_vs_traits; template <class A, int V> struct tmpl_vs_traits<tmpl_vs<A, V>> { using type_arg = A; static constexpr int value_arg = V; }; // 明确匹配tmpl_vs<类型, 整数>的重载 template <class A, int V> void tmpl_func(tmpl_vs<A, V> &) { printf("2\n"); } // 保留通用重载(按需选择) template<class... Args> void tmpl_func(tmpl_vs<Args...> &) { printf("1\n"); } int main(int argc, char* argvp[]) { tmpl_vs<sa, 1> a; tmpl_func(a); // 现在匹配到输出"2"的重载 return 0; }
方案二:使用C++20概念约束
如果使用C++20及以上版本,可通过概念约束明确参数类型,引导编译器匹配目标重载:
#include <stdio.h> #include <concepts> struct sa {}; struct sb {}; template <class a_type, int VS> struct tmpl_vs { static_assert(sizeof(a_type) == 0, "Unexpected."); }; template <int vs> struct tmpl_vs<sa, vs> { void func() { printf("tmpl_vs<sa,vs>\n"); } }; template <int vs> struct tmpl_vs<sb, vs> { void func() { printf("tmpl_vs<sb,vs>\n"); } }; // 通用重载 template<class... Args> void tmpl_func(tmpl_vs<Args...> &) { printf("1\n"); } // 约束:第一个参数为类型,后续为整数非类型参数 template <class A, int... Vs> requires (sizeof...(Vs) >= 1) void tmpl_func(tmpl_vs<A, Vs...> &) { printf("2\n"); } int main(int argc, char* argvp[]) { tmpl_vs<sa, 1> a; tmpl_func(a); // 匹配到带约束的重载,输出"2" return 0; }
内容的提问来源于stack exchange,提问作者shdetect
相关产品推荐
相关产品推荐

