如何使C++预定义类型列表适配可变参数函数调用?
解决折叠表达式无法展开封装类型包的问题
嘿,这个问题我太懂了!你现在的核心困扰在于:my_list并不是你以为的「参数包」,它是一个单独的模板结构体类型——也就是tl<int, float, uint64_t>这个整体。当你把它传给myFunc2时,模板参数Types...就只包含这一个类型,所以折叠表达式自然只会执行一次,打印的是tl结构体本身的大小,而不是里面每个类型的大小。
要让myFunc2<my_list>()这种调用方式生效,我们需要从my_list里提取出它内部封装的参数包,再传递给原函数。下面是两种简单易行的方案:
方案1:用函数重载自动展开(C++17及以上)
我们可以给myFunc2新增一个重载版本,专门接受tl<Types...>类型的实例,然后自动转发到处理参数包的原函数:
#include <iostream> #include <cstdint> template <typename ... Types> struct tl { }; using my_list = tl<int, float, uint64_t>; // 原函数:处理普通类型参数包 template <typename ... Types> void myFunc2() { (std::cout << "Size: " << sizeof(Types) << std::endl, ...); } // 新增重载:专门处理tl类型,自动提取内部参数包 template <typename ... Types> void myFunc2(tl<Types...>) { myFunc2<Types...>(); } int main () { myFunc2<int,uint64_t,bool,uint16_t>(); // 正常打印每个类型大小 myFunc2(my_list{}); // 现在会展开my_list内部的int、float、uint64_t,打印三次 return 0; }
调用时只需要传入一个my_list的临时对象,重载函数会自动推导并展开内部的参数包,完美满足你的需求。
方案2:用类型萃取器提取参数包(更灵活)
如果你希望保持myFunc2<my_list>()这种模板参数调用的写法,可以写一个类型萃取器,把tl内部的参数包提取出来:
#include <iostream> #include <cstdint> #include <tuple> template <typename ... Types> struct tl { }; using my_list = tl<int, float, uint64_t>; // 类型萃取器:提取tl结构体内部的参数包 template <typename T> struct extract_tl_types; template <typename ... Types> struct extract_tl_types<tl<Types...>> { using args_pack = tl<Types...>; }; // 原函数保持不变 template <typename ... Types> void myFunc2() { (std::cout << "Size: " << sizeof(Types) << std::endl, ...); } // 辅助函数:利用萃取器展开参数包 template <typename T> void myFunc2() { // 用C++17的模板lambda来推导参数包 []<typename ... Ts>(tl<Ts...>) { myFunc2<Ts...>(); }(typename extract_tl_types<T>::args_pack{}); } int main () { myFunc2<int,uint64_t,bool,uint16_t>(); // 正常工作 myFunc2<my_list>(); // 现在会展开内部类型,打印三次 return 0; }
这个方案更灵活,如果你后续需要对封装的类型包做更多元编程操作,类型萃取器可以复用。
为什么原来的调用不行?
再补充一句:你原来的myFunc2<my_list>()中,模板参数Types...被推导为[tl<int, float, uint64_t>]——也就是一个包含单个类型的参数包,所以折叠表达式只执行一次,打印的是sizeof(tl<int, float, uint64_t>)(通常是空结构体,大小为1或0,取决于编译器),而不是你期望的每个内部类型的大小。
内容的提问来源于stack exchange,提问作者Waldorf
相关产品推荐
相关产品推荐

