C++中如何传递去掉首元素的std::initializer_list<T>给函数?
嘿,我明白你遇到的麻烦了——std::initializer_list 看起来像容器,但本质上只是编译器生成的临时数组的轻量引用,标准根本没给它提供切片或者从迭代器范围构造的能力,这就是你直接用首尾迭代器构造失败的原因。
下面给你几个可行的解决方案,按推荐程度排序:
如果能修改foo的代码,最好给它加一个接受原始指针和元素个数的重载版本。这样你在bar里就能直接传递从第二个元素开始的指针和剩余元素数量,完全不需要折腾initializer_list:
#include <initializer_list> // 原有的foo实现 template <typename T> void foo(std::initializer_list<T> args) { // 你的原有逻辑,比如遍历元素 for (const auto& elem : args) { // 处理elem } } // 新增重载:支持指针+长度的参数 template <typename T> void foo(const T* elem_ptr, size_t elem_count) { // 直接复用原逻辑,或者在这里实现相同的处理 for (size_t i = 0; i < elem_count; ++i) { // 处理elem_ptr[i] } } template <typename T> void bar(std::initializer_list<T> args) { if (args.empty()) { foo({}); return; } // 直接传递切片后的范围给重载的foo foo(args.begin() + 1, args.size() - 1); }
如果调用bar的地方可以接受直接传参数(比如bar(1,2,3)而不是bar({1,2,3})),那把bar改成可变参数模板会非常简单,直接剥离第一个参数转发给foo就行:
#include <initializer_list> template <typename T> void foo(std::initializer_list<T> args) { // 原foo逻辑 } // 可变参数版本的bar template <typename T, typename... Args> void bar(T first_arg, Args... rest_args) { foo({rest_args...}); // 把剩余参数直接打包成initializer_list传给foo } // 保留原版本处理空列表的情况 template <typename T> void bar(std::initializer_list<T> args) { if (!args.empty()) { // 非空的话其实没法直接转调可变参数版本,所以如果要兼容两种调用方式,还是优先方案1 } foo({}); }
如果既不能改foo,也不能调整bar的调用方式,那只能先把initializer_list的元素复制到vector里。不过这里有个问题:你没法直接把vector转成initializer_list——因为初始化列表是编译时构造的。但如果foo的逻辑只是遍历元素,你可以在bar里自己遍历vector的切片来模拟foo的行为;如果必须调用foo本身,这个方法其实无法满足需求,只能考虑下面的备选方案。
大多数编译器的std::initializer_list内部其实就是一个指针加一个大小,我们可以利用这个实现细节来构造切片后的实例。但要注意:这是未定义行为,标准没规定它的内部结构,换个编译器或者版本可能就失效了,除非万不得已别用:
#include <initializer_list> template <typename T> void foo(std::initializer_list<T> args) { // 原foo逻辑 } template <typename T> std::initializer_list<T> slice_init_list(std::initializer_list<T> list, size_t start) { // 模拟initializer_list的内部结构 struct Impl { const T* ptr; size_t size; }; if (start >= list.size()) { return {}; } Impl sliced{list.begin() + start, list.size() - start}; // 强制类型转换,依赖编译器实现 return reinterpret_cast<std::initializer_list<T>&>(sliced); } template <typename T> void bar(std::initializer_list<T> args) { foo(slice_init_list(args, 1)); }
总结一下:优先选方案1,这是最标准、最安全的做法;如果不能改foo,就看能不能调整bar的调用方式用方案2;方案4尽量别碰,除非你对编译环境有绝对控制权。
内容的提问来源于stack exchange,提问作者Shocky2

