基于std::tuple与折叠表达式实现类Lisp风格的C++函数调用
实现C++中类Lisp风格的嵌套函数调用
你已经迈出了很好的第一步,用std::tuple封装函数和参数的思路完全可行,但当前代码的问题在于没有处理嵌套元组的递归求值——当某个参数本身是另一个函数元组时,你的代码只会把它当作普通参数传递,而不会先执行这个嵌套函数。
解决这个问题的核心是编写一个递归的求值函数,它能自动识别并解析嵌套的函数元组:如果传入的是普通值,直接返回;如果是封装了函数和参数的元组,就先递归求值所有参数(包括嵌套的函数元组),再调用目标函数。
完整实现代码
#include <iostream> #include <tuple> #include <functional> // 基础情况:处理普通值,直接返回 template<typename T> constexpr auto eval(T&& val) { return std::forward<T>(val); } // 递归情况:处理函数元组(第一个元素是可调用对象,第二个是参数元组) template<typename Func, typename... Args> constexpr auto eval(const std::tuple<Func, std::tuple<Args...>>& func_tuple) { // 取出函数和参数元组 const auto& func = std::get<0>(func_tuple); const auto& args_tuple = std::get<1>(func_tuple); // 递归求值每个参数,再传递给函数 return std::apply([&func](auto&&... evaluated_args) { return func(std::forward<decltype(evaluated_args)>(evaluated_args)...); }, std::apply([](auto&&... args) { return std::make_tuple(eval(std::forward<decltype(args)>(args))...); }, args_tuple)); } int main() { // 定义示例函数:求和 auto add = [](auto... args) { return (args + ...); }; // 构造类Lisp嵌套调用:(add 10 5 (add 3 2)) auto nested_call = std::make_tuple( add, std::make_tuple( 10, 5, std::make_tuple(add, std::make_tuple(3, 2)) ) ); // 求值并输出结果 auto result = eval(nested_call); std::cout << result << std::endl; // 输出 20 return 0; }
代码解释
递归
eval函数:- 第一个模板是基础分支,接收普通类型(比如
int、double),直接返回原值。 - 第二个模板专门处理函数元组:先提取函数和参数元组,然后用
std::apply遍历参数元组的每个元素,对每个元素递归调用eval(这样嵌套的函数元组会被先执行),最后把求值后的参数传递给目标函数执行。
- 第一个模板是基础分支,接收普通类型(比如
嵌套调用的构造:
对应Lisp的(fun1 a b (fun2 c d)),我们用std::make_tuple嵌套构造:外层是make_tuple(fun1, make_tuple(a, b, make_tuple(fun2, make_tuple(c, d)))),完全贴合Lisp的嵌套结构。折叠表达式的作用:
你的add函数已经用到了折叠表达式(args + ...)来实现可变参数求和,而我们在eval的参数处理中,用std::make_tuple(eval(args)...)的折叠表达式来批量处理所有参数的递归求值。
扩展说明
- 这个实现支持任意深度的嵌套调用,只要保持
(函数, 参数元组)的结构即可。 - 兼容C++17及以上版本,因为用到了
std::apply、折叠表达式、自动模板参数等特性。 - 可以扩展支持更多类型的可调用对象,比如普通函数、Lambda、成员函数(需要绑定)。
内容的提问来源于stack exchange,提问作者Gustavo Chierici
相关产品推荐
相关产品推荐

