如何特化可变参数模板以跳过非引用参数
客户端-服务端函数调用包装器的引用参数处理方案
问题描述
我正在编写客户端-服务端函数调用的包装器,需要在实际调用前后设置输出参数。现有示例代码无法编译,删除t = 99语句后可正常运行。需求是为AssignRefs或IsolateRef实现模板特化,跳过非引用参数——需要两个函数分别处理非const引用类型、跳过其他参数,想知道有没有简单的实现方式。
当前代码核心问题:
- 若省略函数定义中的
&&,所有参数会丢失引用限定符,IsolateRef调用无效; - 保留
&&时,所有参数都会以引用方式传递给IsolateRef,导致非引用参数(如值传递的int、vector)被尝试赋值,引发编译错误。
示例代码:
#include <iostream> #include <functional> #include <string> #include <vector> void tryp(int a, int b, int &c, std::vector<char> ghost) { c = 7; } void AssignRefs() { std::cout << "P2" << std::endl; } template <typename T> void IsolateRef(T &&t) { t = 99; } template<typename T, typename ... A> void AssignRefs(T &&t, A && ... a) { IsolateRef(t); std::cout << "P1" << std::endl; AssignRefs(a ...); } template<typename ... A> std::function<void(A ...)> wrap(void (*fn)(A ...)) { return [](A ... a) { AssignRefs(a ...); }; } int main() { int z = 6; int y = 4; int a = 9; int b = 77; tryp(1,4,y,std::vector<char>{'H'}); wrap(tryp)(1,2,z,std::vector<char>{'Z'}); std::cout << "Results here: " << y << " :: " << z << " :: " << a << " :: " << b << std::endl; }
解决方案
核心思路是通过编译期类型判断,仅对非const左值引用参数执行赋值操作,其他参数直接跳过。以下提供两种简洁实现方式:
方案1:SFINAE模板重载(兼容C++11及以上)
利用类型萃取和SFINAE机制,为IsolateRef编写两个重载版本,分别处理目标参数和其他参数:
#include <iostream> #include <functional> #include <string> #include <vector> #include <type_traits> void tryp(int a, int b, int &c, std::vector<char> ghost) { c = 7; } void AssignRefs() { std::cout << "P2" << std::endl; } // 通用版本:跳过非引用/const引用参数,什么都不做 template <typename T> std::enable_if_t<!std::is_lvalue_reference_v<T> || std::is_const_v<std::remove_reference_t<T>>> IsolateRef(T &&t) {} // 特化版本:仅处理非const左值引用参数 template <typename T> std::enable_if_t<std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>> IsolateRef(T &&t) { t = 99; } template<typename T, typename ... A> void AssignRefs(T &&t, A && ... a) { IsolateRef(std::forward<T>(t)); // 完美转发保留参数原始类型 std::cout << "P1" << std::endl; AssignRefs(std::forward<A>(a)...); } template<typename ... A> std::function<void(A ...)> wrap(void (*fn)(A ...)) { return [fn](A ... a) { AssignRefs(std::forward<A>(a)...); fn(std::forward<A>(a)...); // 补上原函数调用,包装器才完整 }; } int main() { int z = 6; int y = 4; int a = 9; int b = 77; tryp(1,4,y,std::vector<char>{'H'}); wrap(tryp)(1,2,z,std::vector<char>{'Z'}); std::cout << "Results here: " << y << " :: " << z << " :: " << a << " :: " << b << std::endl; }
方案2:C++17 if constexpr(更简洁)
利用if constexpr在编译期直接判断参数类型,无需多个重载:
#include <iostream> #include <functional> #include <string> #include <vector> #include <type_traits> void tryp(int a, int b, int &c, std::vector<char> ghost) { c = 7; } void AssignRefs() { std::cout << "P2" << std::endl; } template <typename T> void IsolateRef(T &&t) { // 编译期判断:仅对非const左值引用执行赋值 if constexpr (std::is_lvalue_reference_v<T> && !std::is_const_v<std::remove_reference_t<T>>) { t = 99; } // 其他情况自动跳过 } template<typename T, typename ... A> void AssignRefs(T &&t, A && ... a) { IsolateRef(std::forward<T>(t)); std::cout << "P1" << std::endl; AssignRefs(std::forward<A>(a)...); } template<typename ... A> std::function<void(A ...)> wrap(void (*fn)(A ...)) { return [fn](A ... a) { AssignRefs(std::forward<A>(a)...); fn(std::forward<A>(a)...); }; } int main() { int z = 6; int y = 4; int a = 9; int b = 77; tryp(1,4,y,std::vector<char>{'H'}); wrap(tryp)(1,2,z,std::vector<char>{'Z'}); std::cout << "Results here: " << y << " :: " << z << " :: " << a << " :: " << b << std::endl; }
关键说明
- 完美转发:使用
std::forward保留参数的原始值类别(左值/右值),避免引用折叠导致的类型错误; - 类型精准判断:通过
std::is_lvalue_reference_v和std::is_const_v组合,确保只修改可写的输出参数; - 补全原函数调用:原示例的
wrap函数遗漏了原函数fn的调用,解决方案中已补上,保证包装器功能完整。
内容的提问来源于stack exchange,提问作者G B
相关产品推荐
相关产品推荐

