如何用std::tuple实现可终止的编译期函数链?
编译期函数链实现问题:执行失败即停止
需求:实现一个函数链,在编译期接收任意数量的普通函数和成员函数,依次执行它们;若某个处理函数执行失败(返回false)则停止后续执行。
当前遇到的问题:
std::tuple::get不接受运行期计数变量作为模板参数- 代码出现编译错误,无法修改模板参数值
用户提供的代码:
#include <tuple> #include <stdlib.h> template<class...Fs> class Functions { public: Functions(Fs...functions) : m_functions(functions...) { } template<class Arg> void operator()(Arg&& arg) { CallAll<sizeof...(Fs)>(std::forward<Arg>(arg)); } private: std::tuple<Fs...> m_functions; template<size_t index, class Arg> decltype(auto) CallSingle(Arg&& arg) { return std::get<index>(m_functions)(arg); } template<size_t index, class Arg> void CallAll(Arg&& arg) { while (index != 0) { auto ret = CallSingle<index - 1>(std::forward<Arg>(arg)); if (std::is_same<decltype(ret), bool>()) { if (!ret) return; } index--; // error! } } }; int main(int argc, char *argv[]) { Functions f{ [](int x) { return x + 1; }, [](int x) { return false; }, [](int x) { return x + 3; } }; f(42); return 0; }
编译错误信息:
../../main.cpp: In member function 'void Functions<Fs>::CallAll(Arg&&)': ../../main.cpp:90:18: error: decrement of read-only location 'index' 90 | index--; | ^~ ../../main.cpp:90:18: error: lvalue required as decrement operand
错误原因分析
模板参数index是编译期常量,无法在运行时修改值,所以index--这种操作完全不合法。同时std::tuple::get<N>的N必须是编译期确定的常量,不能用运行时变量,这也是为什么不能用while循环遍历tuple的核心原因。
解决方案
方案1:递归模板展开(兼容C++11及以上)
利用模板递归,从索引0开始依次调用函数,直到所有函数执行完毕或某个函数返回false终止。
修改后的代码:
#include <tuple> #include <type_traits> #include <utility> template<class...Fs> class Functions { public: Functions(Fs...functions) : m_functions(std::forward<Fs>(functions)...) { } template<class Arg> void operator()(Arg&& arg) { CallAll<0>(std::forward<Arg>(arg)); } private: std::tuple<Fs...> m_functions; // 调用单个函数 template<size_t index, class Arg> decltype(auto) CallSingle(Arg&& arg) { return std::get<index>(m_functions)(std::forward<Arg>(arg)); } // 递归终止条件:索引等于函数总数,停止执行 template<size_t index, class Arg> typename std::enable_if<index == sizeof...(Fs)>::type CallAll(Arg&&) {} // 递归调用逻辑:执行当前函数,判断是否继续 template<size_t index, class Arg> typename std::enable_if<index < sizeof...(Fs)>::type CallAll(Arg&& arg) { auto ret = CallSingle<index>(std::forward<Arg>(arg)); // 编译期判断返回值是否为bool,避免运行时无效判断 if constexpr (std::is_same_v<std::decay_t<decltype(ret)>, bool>) { if (!ret) { return; // 执行失败,终止后续调用 } } // 执行成功,递归调用下一个函数 CallAll<index + 1>(std::forward<Arg>(arg)); } }; // 成员函数测试示例 struct TestObj { bool check(int x) { return x > 0; } int process(int x) { return x * 2; } }; int main() { // 普通函数+lambda测试 Functions f1{ [](int x) { return x + 1; }, [](int x) { return x > 40; }, // 返回true,继续执行 [](int x) { return false; }, // 返回false,终止后续 [](int x) { return x + 3; } // 不会被执行 }; f1(42); // 成员函数支持:用lambda捕获对象 TestObj obj; Functions f2{ [&obj](int x) { return obj.check(x); }, [&obj](int x) { return obj.process(x); }, [](int x) { return false; } }; f2(10); return 0; }
方案2:C++17折叠表达式(更简洁)
利用C++17的折叠表达式配合std::apply,可以写出更简洁的实现:
#include <tuple> #include <type_traits> #include <utility> template<class...Fs> class Functions { public: Functions(Fs...functions) : m_functions(std::forward<Fs>(functions)...) { } template<class Arg> void operator()(Arg&& arg) { bool continue_exec = true; // 用std::apply展开tuple,折叠表达式依次调用函数 std::apply([&](auto&&...funcs) { ( [&]() { if (!continue_exec) return; auto ret = funcs(std::forward<Arg>(arg)); // 编译期判断返回值类型 if constexpr (std::is_same_v<std::decay_t<decltype(ret)>, bool>) { continue_exec = ret; } }(), ... ); }, m_functions); } private: std::tuple<Fs...> m_functions; }; // 测试代码同方案1 int main() { Functions f{ [](int x) { return x + 1; }, [](int x) { return false; }, [](int x) { return x + 3; } }; f(42); return 0; }
关键要点
- 编译期常量约束:遍历tuple必须使用编译期确定的索引,递归或折叠表达式是标准解决方式
- 返回值判断优化:用
if constexpr在编译期判断函数返回值是否为bool,避免运行时无效分支 - 成员函数支持:可通过lambda捕获对象,或使用
std::bind将成员函数绑定为可调用对象,加入函数链
内容的提问来源于stack exchange,提问作者Caboom Bom
相关产品推荐
相关产品推荐

