sol2 多返回值实战:深入解析 `sol::variadic_results` 的构造、压栈机制与动态返回场景

发布时间:2026/10/15 21:05:27
sol2 多返回值实战:深入解析 `sol::variadic_results` 的构造、压栈机制与动态返回场景 游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载导读sol::variadic_results是 sol2 Lua 绑定库提供的多返回值容器它允许 C 函数在返回给 Lua 之前动态准备任意数量、任意类型的返回值突破std::tuple只能返回固定类型组合、sol::as_returns只能返回同质容器元素的限制。本文以 sol2 3.3.0随 Piccolo 引擎以engine/3rdparty/sol2-3.3.0方式内置的官方 API 文档 variadic_results.rst 为主体结合 variadic_results.hpp 源码实现、multi_results.cpp 官方示例与 variadics.cpp 运行时测试完整讲解该类型的声明语义、五种构造途径、底层压栈原理以及按运行时条件返回不同数量/不同类型结果的实战写法。读完本文你将能在自己的 sol2 绑定代码中自如地使用sol::variadic_results实现动态多返回值。一、sol::variadic_results是什么先看官方文档定义官方 API 文档 variadic_results.rst 对该类型给出了精炼的定义struct variadic_results : std::vectorobject { ... };文档明确指出它的核心定位push multiple disparate arguments into lua——把多个彼此类型不同的参数一次性压入 Lua它允许开发者在返回给 Lua 之前预先准备好多个返回值prepare multiple returns before returning them into Lua它派生自std::vector因此可以完全像std::vector一样使用push_back、insert、begin/end迭代器等容器中的对象可以使用sol::object的各种构造函数及函数来添加详见 object.rst。在 sol2 的 functions.rst 中官方把向 Lua 返回多个值的途径归纳为两条返回std::tuple或使用sol::variadic_results。而 all-the-things.rst 则进一步点明其典型场景是returning multiple differently-typed arguments返回多个不同类型的参数。这意味着它解决的是返回值类型异构 数量运行时才确定这一类问题。二、源码视角继承关系与完整的构造函数族文档给出的struct variadic_results : std::vectorobject只是示意图真正的实现位于 variadic_results.hpp它分成了两层// 第一层带分配器模板参数的泛型基类 template typename Al typename std::allocatorobject struct basic_variadic_results : public std::vectorobject, Al { using base_t std::vectorobject, Al; basic_variadic_results(); // 默认构造 // 从 unsafe_function_result 批量接管返回值 basic_variadic_results(unsafe_function_result fr); // 从 protected_function_result 批量接管返回值 basic_variadic_results(protected_function_result fr); // 变参构造直接转发给 std::vector 的区间/填充构造 template typename Arg0, typename... Args, ... basic_variadic_results(Arg0 arg0, Args... args); }; // 第二层面向用户的便捷别名 struct variadic_results : public basic_variadic_results { using base_t::basic_variadic_results; // 继承全部构造函数 };从源码结构可以推断设计上刻意保留了两层basic_variadic_resultsAl允许用户通过自定义分配器Al控制sol::object的内存分配策略默认是std::allocatorobject而variadic_results则是使用默认分配器的即用类型。实际使用时直接写sol::variadic_results即可。构造途径逐一解析1. 默认构造——得到空容器之后用push_back/insert填充sol::variadic_results values; values.push_back({ L, sol::in_place_typeint, 42 });2. 从unsafe_function_result/protected_function_result构造——这是非常有用的返回值搬运能力当你在 C 里调用了一个 Lua 函数得到unsafe_function_result或protected_function_result想把它产生的一串返回值原样再转交出去时可以直接构造sol::variadic_results(results_of_lua_call); // 自动 reserve insert 所有返回值其实现为this-reserve(fr.return_count()); this-insert(this-cend(), fr.begin(), fr.end());即先按返回值数量预分配容量再把结果迭代器范围内的每个元素插入。unsafe_function_result见 unsafe_function_result.hpp本身提供了begin/end迭代器与stack_proxy元素类型因此这一转换是零成本语义上的换壳。3. 变参转发构造——把任意参数直接转发给std::vectorobject, Al的对应构造函数。这正是官方测试tests/run_time/source/variadics.cpp中迭代器区间构造得以工作的入口lua.set_function(f, [](sol::variadic_args args) { return sol::variadic_results(args.cbegin(), args.cend()); });该测试第 178–202 行验证了Lua 侧执行v1, v2, v3 f(1, bark, true)后C 侧可分别取回int v1 1、std::string v2 bark、bool v3 true且f(25, 82)返回两个值时v4, v5也能正确接收——返回值的数量完全由传入参数的数量动态决定。值得注意的是这个变参构造函数通过 SFINAE 禁用了三类实参basic_variadic_results自身、function_result、protected_function_result防止自拷贝与歧义这些实参会走专门的重载或拷贝/移动路径。三、元素怎么放进去sol::object与sol::in_place构造由于variadic_results的元素类型是sol::object添加元素实际上就是构造sol::object。object.rst 列出了四种构造方式其中与variadic_results配合最紧密的是两条in_place构造object(lua_State* L, in_place_t, T arg, Args... args); object(lua_State* L, in_place_type_tT, Args... args);{ L, sol::in_place, awoo }按字面值类型就地构造一个对象{ L, sol::in_place_typeint, a }显式指定目标类型T把变量a转换为该类型后构造。为什么需要传入Llua_State*因为sol::object需要在指定的 Lua 状态上把值压栈、从栈上建立引用。而绑定到 Lua 的 C 函数里如何拿到当前的L答案是透明参数sol::this_state见 this_state.rst它作为函数签名的一部分调用时由 sol2 自动注入Lua 侧无需感知。官方示例 multi_results.cpp 正是这一写法的标准范式lua.set_function( multi_vars, [](int a, bool b, sol::this_state L) { sol::variadic_results values; values.push_back({ L, sol::in_place_typeint, a }); values.push_back({ L, sol::in_place_typebool, b }); values.push_back({ L, sol::in_place, awoo }); return values; });Lua 侧一次接收三个类型各不相同的返回值t, u, v multi_vars(42, true) -- t 42, u true, v awoo官方示例随后在 C 侧断言sol_c_assert(t 42); sol_c_assert(u); sol_c_assert(v awoo);证明三种类型均被正确还原。四、底层原理stack::unqualified_pusher与is_container特化variadic_results之所以能一个返回值展开成多个 Lua 返回值关键在于 variadic_results.hpp 末尾对 sol2 栈推送机制的定制template struct is_containervariadic_results : std::false_type { }; namespace stack { template struct unqualified_pushervariadic_results { int push(lua_State* L, const variadic_results r) { using base_t basic_variadic_results; return stack::push(L, static_castconst base_t(r)); } }; }对应basic_variadic_resultsAl的 pusher 实现则逐元素压栈并累加数量int push(lua_State* L, const basic_variadic_resultsAl e) { int p 0; for (const auto i : e) { p stack::push(L, i); } return p; }从源码结构可以梳理出两条关键机制stack::unqualified_pusher决定如何返回sol2 约定stack::push的返回值是该类型在 Lua 栈上占用的元素个数。variadic_results的 pusher 遍历容器内每个sol::object并逐个压栈把p压入的总数作为返回值交给 Lua 运行时Lua 便据此把栈顶这p个值全部当作函数的多重返回值。这正是一个 C 返回值展开为 N 个 Lua 返回值的底层实现。is_containervariadic_results false_type决定不被当作普通容器sol2 默认会把可迭代容器按表/数组方式转换is_container特化为std::false_type后sol2 就不会把variadic_results误当成std::vectorsol::object那样的容器 usertype而是走专门的 pusher 逻辑。这一对特化variadic_results.hpp是整个类型行为正确性的关键。五、完整可运行示例三种多返回值方案对比官方 multi_results.cpp 一次性演示了 sol2 的全部三种多返回值手段是理解variadic_results定位的最佳参照物方案适用场景局限返回std::tuple返回值数量、类型在编译期固定无法按运行时条件改变数量sol::as_returns(container)见 as_returns.rst返回值来自任意可迭代容器std::vector、std::set、std::deque等元素类型通常同质sol::variadic_results动态数量 异构类型需要精确控制每个返回值需手动push_back每个元素as_returns的官方示例 as_returns.cpp 展示了容器方案std::setstd::string results {arf, bark, woof}经sol::as_returns(std::move(results))返回后Lua 侧v1, v2, v3 f()按序接收三个字符串。而as_returns.rst明确写着要更精细地控制返回值请使用sol::variadic_resultsTo have more control over the returns, usesol::variadic_results——这正说明二者的分工容器方案求便捷variadic_results求精确与异构。动态数量返回的完整示例参考官方示例与测试一个典型的按条件返回不同数量和类型的函数如下#define SOL_ALL_SAFETIES_ON 1 #include sol/sol.hpp int main() { sol::state lua; lua.open_libraries(sol::lib::base); lua.set_function(multi_vars, [](int a, bool b, sol::this_state L) { sol::variadic_results values; values.push_back({ L, sol::in_place_typeint, a }); values.push_back({ L, sol::in_place_typebool, b }); values.push_back({ L, sol::in_place, awoo }); // 字符串按字面类型就地构造 return values; }); // Lua 侧多返回值接收 lua.script(R( t, u, v multi_vars(42, true) assert(t 42 and u true and v awoo) )); return 0; }再结合运行时测试中的写法还可以用insert在容器任意位置追加元素variadics.cpplua.set_function(f, [](sol::this_state ts, bool maybe) { sol::variadic_results vr; if (maybe) { vr.push_back({ ts, sol::in_place, 1 }); vr.push_back({ ts, sol::in_place, 2 }); vr.insert(vr.cend(), { ts, sol::in_place, 3 }); // 用迭代器位置插入 return vr; // Lua 侧收到 3 个返回值 } else { vr.push_back({ ts, sol::in_place, bark }); vr.push_back({ ts, sol::in_place, woof }); vr.insert(vr.cend(), { ts, sol::in_place, arf }); vr.push_back({ ts, sol::in_place, borf }); return vr; // Lua 侧收到 4 个返回值 } });该测试随后用lua.safe_script执行v1, v2, v3 f(true)与v4, v5, v6, v7 f(false)并逐一断言还原出的int/std::string值完整验证了同一函数、不同输入、不同数量的异构返回值这一核心能力。六、在 Piccolo 引擎中的落地场景sol::variadic_results所属的 sol2 3.3.0 以第三方依赖形式内置于 Piccolo 引擎的 engine/3rdparty/sol2-3.3.0 目录其 CMake 集成见 sol2 CMakeLists.txt。引擎运行时确实在 Lua 脚本组件中直接使用了 sol2见 lua_component.cpp其中postLoadResource打开sol::lib::base基础库、通过m_lua_state.set_function注册set_float/get_bool/invoke等 C 函数并在tick中逐帧执行m_lua_state.script(m_lua_script)m_lua_state的类型为sol::state见 lua_component.h。在这种C 引擎逻辑 ↔ Lua 脚本双向绑定的架构里variadic_results的典型用法是C 侧的查询/计算函数一次向 Lua 脚本返回多个异构结果例如同时返回整数 ID、布尔状态与字符串名称或在 C 内部先拿到 Lua 函数的返回结果unsafe_function_result/protected_function_result后原样转发给另一处 Lua 调用。对于游戏对象属性查询这类一问多答的脚本接口它能省去 Lua 侧手工组表/拆表的开销。七、注意事项与最佳实践元素一律是sol::objectvariadic_results的元素类型固定为sol::object由基类std::vectorobject决定push 前需通过in_place/in_place_type或从其它 sol 引用类型构造好object。必须持有lua_State*构造元素需要当前状态务必在函数签名中加入sol::this_state L透明参数该参数由 sol2 注入Lua 侧无需传值官方示例注释明确说明 this_state 是 transparent argument。不要用裸std::vectorsol::object替代没有stack::unqualified_pusher特化时容器会被 sol2 当作 Lua 表处理无法产生多返回值variadic_results的is_container false_type特化正是为隔离这种默认行为而存在。安全性开关官方示例统一在 include 前定义#define SOL_ALL_SAFETIES_ON 1开启后 sol2 会补充类型检查等安全机制详见 functions.rst 的 function call safety 一节。选择正确的多返回值工具编译期定长用std::tuple同质容器展开用sol::as_returns运行时变长、异构、需精细控制时用sol::variadic_results——三者按需取用不要一律套用。总结sol::variadic_results是 sol2 多返回值体系中灵活性最高的一环它以std::vectorsol::object为存储骨架借助stack::unqualified_pusher的逐元素压栈与is_container的负向特化实现了动态数量 异构类型的 Lua 多返回值语义配合sol::in_place构造与sol::this_state透明参数可以写出既精确又直观的绑定代码。官方文档、头文件实现、multi_results.cpp 示例与 variadics.cpp 测试互为印证构成了理解与使用该 API 的完整证据链。在 Piccolo 这类C 引擎 Lua 脚本混合架构中它正是连接两侧异构数据流的实用桥梁。赞分享游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载相关推荐sol2 中的 variadic_results用 C 灵活构造 Lua 多返回值sol2 中的 variadic_results用 C 灵活构造 Lua 多返回值 导读 sol::variadic_results 是 sol2Sol开发工具Piccolo 引擎集成指南sol2 库 sol::tie 多返回值绑定全解析Piccolo 引擎集成指南sol2 库 sol::tie 多返回值绑定全解析 导读 sol::tie 是 sol2Lua 绑定库提供的 std::tie游戏开发图形学sol2 中的 sol::as_returns将 C 可迭代容器展开为 Lua 多返回值sol2 中的 sol::as_returns将 C 可迭代容器展开为 Lua 多返回值 导读 sol::as_returns 是 sol2sol3库开发工具上一篇Claude Desktop Linux 安装指南Fedora/Arch 用户的四条路径与避坑要点下一篇TV-Bro把遥控器当鼠标用的免费开源电视浏览器3 步上手创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询