C++可变参数函数预加载参数的Wrapper实现难题
我尝试封装一个可变参数函数,实现先传入任意数量的参数、后续再补充剩余参数的功能,最终希望能这样使用:
template<class... Args> auto someFunction(Args... args) { // 处理参数 } template<class... Args> auto differentFunction(Args... args) { // 用不同逻辑处理参数 } auto partial = Wrapper<someFunction>(arg1, arg2, /* 更多参数... */); auto otherPartial = Wrapper<someFunction>(arg3, arg4, /* 更多参数... */); auto differentPartial = Wrapper<differentFunction>(arg5, arg6, /* 更多参数... */);
之后可以存储partial、otherPartial等对象,后续调用时补充剩余参数:
auto result1 = partial(argA, argB, /* 更多参数... */); // 等价于someFunction(arg1, arg2, ..., argA, argB, ...); auto result2 = partial(argC, argD, /* 更多参数... */); // 等价于someFunction(arg1, arg2, ..., argC, argD, ...); auto otherResult = otherPartial(argP, argQ, /* 更多参数... */); // 等价于someFunction(arg3, arg4, ..., argP, argQ, ...); auto differentResult = differentPartial(argX, argY, /* 更多参数... */); // 等价于differentFunction(arg5, arg6, ..., argX, argY, ...);
所有目标函数返回类型相同,参数类型也一致,目前的难点是实现Wrapper。
我用简单的sum函数做了最小实现尝试:
int sum() { return 0; } int sum(int a) { return a; } int sum(int a, int b) { return a + b; } template<typename... Args> int sum(int a, Args... rest) { return a + sum(rest...); }
随后用std::bind_front为sum预加载若干参数:
#include <iostream> #include <functional> template<typename... Inputs> auto WrapSum(Inputs... numbers) { return std::bind_front( [](auto&&... args){ return sum(std::forward<decltype(args)>(args)...); }, numbers... ); } auto sumMore = WrapSum(10, 20, 77); std::cout << "Empty = " << sumMore() << "\n"; // 输出107 std::cout << "One = " << sumMore(1) << "\n"; // 输出108 std::cout << "Two = " << sumMore(12, 99) << "\n"; // 输出218 std::cout << "Many = " << sumMore(10, 10, 10, 10, 10, 10) << "\n"; // 输出167 // WrapSum()、WrapSum(7)等写法也能正常工作
为了把逻辑适配到其他同签名的函数,我需要替换sum,但直接传递sum作为参数无法成功(就算把auto Func换成std::function<int(Inputs...)> Func也不行):
template<typename... Inputs> auto Wrapper(auto Func, Inputs... numbers) { return std::bind_front( [Func](auto&&... args){ return Func(std::forward<decltype(args)>(args)...); }, numbers... ); }; auto sumMore = Wrapper(&sum, 10, 20, 77); // 编译错误!
我更偏好模板仿函数的写法(调用语法更简洁),但同样无法成功:
template<auto Func> struct Wrapper { template<typename... Inputs> auto operator()(Inputs... numbers) const { return std::bind_front( [](auto&&... args){ return Func(std::forward<decltype(args)>(args)...); }, numbers... ); } }; auto sumMore = Wrapper<sum>(10, 20, 77); // 编译错误!
两种写法单独编译能通过,但调用时会出现编译错误:No matching function for call to 'Wrapper',详细信息为Candidate function [with Inputs = <int, int, int>] not viable: no overload of 'sum' matching 'std::function<int (int, int, int)>' for 1st argument。
我是C++新手,推测编译器在传递重载函数作为参数时,无法识别正确的重载版本,但直接使用时却没问题(比如WrapSum里的情况)。
如果推测正确,有没有办法帮编译器解决这个问题?
如果推测错误,有没有其他实现目标的方法?(最好不用宏,除非别无选择)
感觉这应该是可以实现的,如果确实不行,希望能了解原因。
编辑:我了解到有回答介绍用LIFT宏封装标准库函数并作为参数传递,但我认为这个问题和那个不重复——虽然解决方案可能适用,但场景不同(封装自定义可变参数函数 vs 封装标准函数),不了解问题根源的人很难找到那个回答(我自己就没找到)。
现在我对LIFT宏的工作原理有了更深的理解(感谢评论者),打算尝试为Wrap<someFunction>(args...)的写法实现无宏解决方案。
你的推测完全正确:重载函数(包括你写的多个sum重载和可变参数模板sum)本质是一组函数的集合,编译器无法直接把这个集合作为单一实体传递给模板参数或函数参数——只有在明确的调用上下文里,编译器才能根据参数类型匹配到具体的重载版本,比如WrapSum里直接调用sum(...)时,编译器能根据传入的参数推导出要调用哪个sum。
下面提供几种无宏的解决方案,适配你想要的Wrapper<Func>(args...)写法:
方案1:用lambda作为模板参数传递目标函数
把目标函数包装成一个泛型lambda,作为Wrapper的模板参数,绕过重载函数无法直接传递的问题:
#include <functional> #include <iostream> // 原sum函数保持不变 int sum() { return 0; } int sum(int a) { return a; } int sum(int a, int b) { return a + b; } template<typename... Args> int sum(int a, Args... rest) { return a + sum(rest...); } // 通用Wrapper实现 template<auto FuncLambda> struct Wrapper { template<typename... Inputs> auto operator()(Inputs&&... numbers) const { return std::bind_front( [](auto&&... args) { return FuncLambda(std::forward<decltype(args)>(args)...); }, std::forward<Inputs>(numbers)... ); } }; // 使用方式 int main() { // 把sum包装成泛型lambda作为模板参数 auto sumMore = Wrapper<[](auto&&... args) { return sum(std::forward<decltype(args)>(args)...); }>(10, 20, 77); std::cout << "Empty = " << sumMore() << "\n"; // 107 std::cout << "One = " << sumMore(1) << "\n"; // 108 std::cout << "Two = " << sumMore(12, 99) << "\n"; // 218 // 适配otherFunction的例子 template<class... Args> auto otherFunction(Args... args) { // 示例逻辑:返回参数数量 return sizeof...(args); } auto otherPartial = Wrapper<[](auto&&... args) { return otherFunction(std::forward<decltype(args)>(args)...); }>(1, 2, 3); std::cout << otherPartial(4,5) << "\n"; // 输出5(3+2个参数) return 0; }
核心是用泛型lambda捕获整个重载集合——lambda的auto&&... args参数会在调用时触发重载决议,让编译器自动匹配正确的目标函数版本。
方案2:把目标函数包装成仿函数
如果可以修改目标函数的定义,把重载函数改成一个泛型仿函数,就能直接作为模板参数传递:
#include <functional> #include <iostream> // 把sum改成泛型仿函数 struct Sum { int operator()() const { return 0; } int operator()(int a) const { return a; } int operator()(int a, int b) const { return a + b; } template<typename... Args> int operator()(int a, Args... rest) const { return a + (*this)(rest...); } }; // 通用Wrapper实现 template<typename Func> struct Wrapper { template<typename... Inputs> auto operator()(Inputs&&... numbers) const { return std::bind_front( [](auto&&... args) { return Func{}(std::forward<decltype(args)>(args)...); }, std::forward<Inputs>(numbers)... ); } }; // 使用方式 int main() { auto sumMore = Wrapper<Sum>(10, 20, 77); std::cout << "Empty = " << sumMore() << "\n"; // 107 std::cout << "One = " << sumMore(1) << "\n"; // 108 // 适配otherFunction的仿函数版本 struct OtherFunction { template<class... Args> auto operator()(Args... args) const { return sizeof...(args); } }; auto otherPartial = Wrapper<OtherFunction>(1,2); std::cout << otherPartial(3,4,5) << "\n"; // 输出5 return 0; }
仿函数本身是单一实体,编译器可以直接将其作为模板参数处理,不需要额外的lambda包裹,写法更简洁。
方案3:简化版直接生成绑定对象
如果不需要Wrapper<Func>的模板语法,也可以直接写一个通用函数模板,接收泛型lambda作为参数,使用更灵活:
#include <functional> #include <iostream> // 原sum函数保持不变 int sum() { return 0; } int sum(int a) { return a; } int sum(int a, int b) { return a + b; } template<typename... Args> int sum(int a, Args... rest) { return a + sum(rest...); } // 通用绑定函数 template<typename Func, typename... Inputs> auto make_partial(Func&& func, Inputs&&... numbers) { return std::bind_front( std::forward<Func>(func), std::forward<Inputs>(numbers)... ); } // 使用方式 int main() { auto sumMore = make_partial( [](auto&&... args) { return sum(std::forward<decltype(args)>(args)...); }, 10, 20, 77 ); std::cout << sumMore() << "\n"; // 107 std::cout << sumMore(1,2) << "\n"; // 110 return 0; }
原写法失败的原因
你之前的写法中,Wrapper<sum>或Wrapper(&sum, ...)要求编译器从sum的重载集合中选出一个具体函数,但此时没有任何调用上下文(比如传入的参数类型),编译器无法确定选哪个重载版本,因此报错。而WrapSum里直接调用sum(...)时,编译器能根据bind_front后续传入的参数类型进行重载决议,所以能正常工作。
内容的提问来源于stack exchange,提问作者Andrew Carter

