C++中如何向模板传递重载函数名以复用其不同重载版本?
解决重载回调函数的重复定义问题
首先,你的核心痛点是重载函数无法直接作为模板参数传递——编译器需要明确的函数指针类型,而重载集本身没有单一类型。不用宏或代码生成的话,我们可以通过几个C++特性来简化代码,下面是具体方案:
方案1:用泛型Lambda包装重载集(C++17+)
我们可以把重载的成员函数包装在一个泛型Lambda里,让编译器根据调用时的参数类型自动匹配对应的重载版本。然后修改createCallbacks来接受这个Lambda,生成所需的回调对:
// 修改createCallbacks模板,接受任意函数对象 template<typename Handler> auto MyProxy::createCallbacks(Handler&& handler) { // 返回一个"工厂函数",用来指定成功回调的参数类型 return [handler = std::forward<Handler>(handler)]<typename SuccessT>() { auto onSuccess = [handler](SuccessT value) { handler(std::move(value)); }; auto onError = [handler](std::string value) { handler(std::move(value)); }; return std::make_pair( std::function<void(SuccessT)>(std::move(onSuccess)), std::function<void(std::string)>(std::move(onError)) ); }; } // 使用方式 void MyProxy::action1() { // 用泛型Lambda包装重载的answer函数 auto callbacks = createCallbacks([this](auto&& val) { using ValType = std::decay_t<decltype(val)>; if constexpr (std::is_same_v<ValType, int>) { this->answerCallToDoSmth1(std::forward<decltype(val)>(val)); } else if constexpr (std::is_same_v<ValType, std::string>) { this->answerCallToDoSmth1(std::forward<decltype(val)>(val)); } }).template operator()<int>(); // 指定成功回调的参数类型 SomeLargeClass::callToDoSmth1(callbacks.first, callbacks.second); }
这个方案的好处是只需要写一次Lambda包装,后续调用只需要指定成功参数类型即可,避免了重复定义两个回调Lambda。
方案2:用OverloadPair结构体提取重载函数指针(C++17+)
如果你觉得泛型Lambda的分支有点繁琐,可以定义一个简单的结构体,用来显式提取重载的两个函数指针,利用C++17的类模板参数推导(CTAD)来简化类型声明:
// 定义重载对结构体 template<typename SuccessFn, typename ErrorFn> struct OverloadPair { SuccessFn success; ErrorFn error; }; // 为OverloadPair添加CTAD推导规则 template<typename Class, typename SuccessArg> OverloadPair(void (Class::*)(SuccessArg), void (Class::*)(std::string)) -> OverloadPair<void (Class::*)(SuccessArg), void (Class::*)(std::string)>; // 修改createCallbacks接受OverloadPair template<typename SuccessFn, typename ErrorFn> auto MyProxy::createCallbacks(OverloadPair<SuccessFn, ErrorFn> pair) { auto onSuccess = [this, fn = pair.success](auto value) { (this->*fn)(std::move(value)); }; auto onError = [this, fn = pair.error](std::string value) { (this->*fn)(std::move(value)); }; return std::make_pair( std::function(std::move(onSuccess)), std::function(std::move(onError)) ); } // 使用方式 void MyProxy::action1() { // 编译器会自动推导OverloadPair的类型 auto callbacks = createCallbacks(OverloadPair{ &ResponsesClass::answerCallToDoSmth1, &ResponsesClass::answerCallToDoSmth1 }); SomeLargeClass::callToDoSmth1(callbacks.first, callbacks.second); }
这里虽然写了两次函数名,但编译器会根据OverloadPair的推导规则自动匹配对应的重载版本,比手动写两个Lambda要简洁得多。
方案3:C++20+的简化版本
如果可以使用C++20,我们可以结合std::invoke和泛型Lambda,进一步简化代码,不需要显式的分支判断:
template<typename Handler> auto MyProxy::createCallbacks(Handler&& handler) { return [handler = std::forward<Handler>(handler)]<typename SuccessT>() { return std::make_pair( std::function<void(SuccessT)>([handler](SuccessT val) { std::invoke(handler, std::move(val)); }), std::function<void(std::string)>([handler](std::string val) { std::invoke(handler, std::move(val)); }) ); }; } // 使用方式 void MyProxy::action1() { auto callbacks = createCallbacks([this](auto&& val) { this->answerCallToDoSmth1(std::forward<decltype(val)>(val)); }).template operator()<int>(); SomeLargeClass::callToDoSmth1(callbacks.first, callbacks.second); }
C++20的std::invoke会自动处理成员函数的调用,加上泛型Lambda的自动类型推导,代码会更简洁。
关于未来C++标准的计划
目前C++标准委员会正在讨论几个针对重载集传递的提案,比如:
- P2082R0: 允许Lambda直接捕获重载集,这样你可以直接把
&ResponsesClass::answerCallToDoSmth1传递给Lambda,不需要额外包装。 - P1170R0: 允许重载集作为函数参数直接传递,模板可以根据上下文自动选择对应的重载版本。
这些提案还处于早期讨论阶段,不一定会进入C++26,但方向是让重载集的使用更直观,减少开发者的冗余代码。
内容的提问来源于stack exchange,提问作者Алексей Смиян
相关产品推荐
相关产品推荐

