在C++中,有一些不太常见的小技巧,甚至有些能够被称之为黑魔法
判断类有没有某个方法,有这个方法才去调用
当你的代码需要同时服务某个库的新旧版本,希望调用新增接口,又不希望这个库的旧版本因为没有这个接口报错
对C++的基础库开发者来说,这是很常见的需求,因为你的库依赖的底层库,不一定可以强行指定版本,那么此时这个兼容性神器就可以出场了
这是一个简单的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <type_traits> template <typename ...>using void_t_cpp11 = void ;template <typename T, typename = void >struct isNewVersion : std::false_type {};template <typename T>struct isNewVersion <T, void_t_cpp11<decltype (std::declval <T>().newVersionInterface ())>> : std::true_type {}; template <typename T, typename enable_if<isNewVersion<T>::value>::type* = nullptr > void newVersionInterface (T &t) { t.newVersionInterface (); } template <typename T, typename enable_if<!isNewVersion<T>::value>::type* = nullptr > void newVersionInterface (T &t) { }
以上代码可以在C++11下正常工作
isNewVersion这个模板类有两个模板参数,其中typename T
是被判断的类型,第二个模板参数要结合偏特化来看
isNewVersion这个模板类的偏特化的第二个参数是void_t_cpp11(如果在C++17下,可以直接使用std::void_t ),void_t_cpp11在传入任何类型下都将返回void,因此isNewVersion的偏特化就是基于void类型的
void_t_cpp11内是一个decltype表达式,用declval创建了T类型,调用newVersionInterface,如果T类型存在newVersionInterface,那么decltype表达式成立,这个偏特化就可以成立
非偏特化版本的isNewVersion的第二个模板参数是默认模板参数typename = void
,因此使用一个参数的isNewVersion<T>
相当于isNewVersion<T, void>
,会默认指向void偏特化类型,根据SFINAE,如果偏特化成立,就会匹配到偏特化的版本,继承了true_type,否则降级到非偏特化版本,继承了false_type
这两个继承的type都提供了一个false或者true的value字段,因此根据isNewVersion<T>::value
就可以得到T类型是否实现了newVersionInterface方法
随后再次使用SFINAE实现一个模板函数newVersionInterface,当isNewVersion<T>::value
为true时,就可以对t调用newVersionInterface方法,否则什么都不做
将static_assert的注释去掉,写一个测试程序验证一下
1 2 3 4 5 6 7 8 9 10 11 struct A { void newVersionInterface () ; }; struct B {}; A a; newVersionInterface (a);B b; newVersionInterface (b);
编译得到
1 2 3 4 5 6 7 8 9 10 /root/cpp_test/temp.cpp: In instantiation of ‘void newVersionInterface(T&) [with T = A; typename std::enable_if<isNewVersion<T>::value>::type* <anonymous> = 0]’: /root/cpp_test/temp.cpp:37:26: required from here /root/cpp_test/temp.cpp:25:29: error: static assertion failed: newVersionInterface found 25 | static_assert(sizeof(t) == 0, "newVersionInterface found"); | ~~~~~~~~~~^~~~ /root/cpp_test/temp.cpp: In instantiation of ‘void newVersionInterface(T&) [with T = B; typename std::enable_if<(! isNewVersion<T>::value)>::type* <anonymous> = 0]’: /root/cpp_test/temp.cpp:39:26: required from here /root/cpp_test/temp.cpp:30:29: error: static assertion failed: newVersionInterface not found 30 | static_assert(sizeof(t) == 0, "newVersionInterface not found"); | ~~~~~~~~~~^~~~
可以看到A类assert到存在newVersionInterface方法,B类assert到不存在,符合预期
static_assert
这里的 static_assert
利用了 C++
模板的二阶段查找(two-phase name lookup)特性,在 C++
的模板编译过程中,会分成模板参数检查阶段和模板实例化阶段
在模板参数检查阶段,编译器不会具体检查模板函数体中的依赖模板的表达式,只有模板函数被具体的类型实例化时,才会检查函数体中依赖模板的表达式
static_assert(sizeof(t) == 0,
"xxx")中的sizeof(t)一定是不为0的,所以在某个类型具体实例化的时候,才会因为assert出希望打印的字符串
实际使用场景
需求:
函数getMember有两个参数,第一个值参数和第二个该值的成员指针参数
伪代码如下:
1 2 template <typename T, typename MemberPtr>auto & getMember (T &t, MemberPtr ptr) ;
值参数有可能传入原生指针或者智能指针,要能正确区分最后获取到该值的成员
第一步是判断类型是不是指针,如果是的话获取解引用类型,否则获取当前类型
这就需要通过非原生指针类型存在operator->()
方法,从而实现isPtr模板类,随后再使用偏特化模板类实现getPtrType模板类
第二步就是通过getPtrType模板类进行函数重载,实现getMember函数
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <type_traits> template <typename ...>using void_t_cpp11 = void ;template <typename T, typename = void >struct isCustomPtr : public std::false_type {};template <typename T>struct isCustomPtr <T, void_t_cpp11<decltype (std::declval <T>().operator ->())>> : public std::true_type { };
测试一下isPtr
1 2 3 4 5 6 7 8 9 #include <iostream> #include <memory> using namespace std;int main () { cout << isCustomPtr<int >::value << endl; cout << isCustomPtr<shared_ptr<int >>::value << endl; }
输出
符合预期,接下来是实现getPtrType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template <typename T, typename = void >struct getPtrType { using type = T; }; template <typename T>struct getPtrType <T, typename std::enable_if<std::is_pointer<T>::value>::type> { using type = typename std::remove_reference<decltype (*std::declval <T>())>::type; }; template <typename T>struct getPtrType <T, typename std::enable_if<isCustomPtr<T>::value>::type> { using type = typename std::remove_reference<decltype (*std::declval <T>().operator ->())>::type; };
这里的原理和isCustomPtr是类似的,非偏特化版本的第二个默认模板参数为void
随后如果enable_if能够满足,那么就会变成void类型,所以getPtrType<T>
的默认void就会指向这个偏特化类型
编译器打印一下类型验证一下
编译器打印模板参数类型print_type
1 2 3 4 5 6 7 8 9 10 11 template <typename >struct print_type ;int main () { { print_type<getPtrType<int *>::type> dummy[1 ]; } { print_type<getPtrType<shared_ptr<int >>::type> dummy[1 ]; } }
这也是一个很常用的小技巧,通过构造一个不完整类型的数组(单个元素不行),可以让编译器报错打印出类型
1 2 3 4 5 6 7 8 9 /root/cpp_test/temp.cpp: In function ‘int main()’: /root/cpp_test/temp.cpp:70:44: error: elements of array ‘print_type<int> dummy [1]’ have incomplete type 70 | print_type<getPtrType<int*>::type> dummy[1]; | ^~~~~ /root/cpp_test/temp.cpp:70:44: error: storage size of ‘dummy’ isn’t known /root/cpp_test/temp.cpp:73:55: error: elements of array ‘print_type<int> dummy [1]’ have incomplete type 73 | print_type<getPtrType<shared_ptr<int>>::type> dummy[1]; | ^~~~~ /root/cpp_test/temp.cpp:73:55: error: storage size of ‘dummy’ isn’t known
可以看到两次print_type数组,都报错出print_type<int> dummy [1] have incomplete type
,print_type内的int类型就是getPtrType<T>::type
预期获取的类型
getMember实现
getMember的实现相对简单,唯一要注意的是指针类型可能传入shared_ptr<const T>
类型,从而匹配非const函数,因此返回值需要返回auto &
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct A { int a = 0 ; }; template <typename K, typename T>K& getMember (T &t, K T::* ptr) { return t.*ptr; } template <typename K, typename T>const K& getMember (const T &t, K T::* ptr) { return t.*ptr; } template <typename K, typename T>auto & getMember (T &t, K getPtrType<T>::type::* ptr) { return (*t).*ptr; } template <typename K, typename T>const K& getMember (const T &t, K getPtrType<T>::type::* ptr) { return (*t).*ptr; }
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using namespace std;int main () { A a; cout << getMember (a, &A::a) << endl; { const A a; cout << getMember (a, &A::a) << endl; } { auto aPtr = make_shared <A>(); cout << getMember (aPtr, &A::a) << endl; } { auto aPtr = make_shared <const A>(); cout << getMember (aPtr, &A::a) << endl; } { A* aPtr = &a; cout << getMember (aPtr, &A::a) << endl; } { const A* aPtr = &a; cout << getMember (aPtr, &A::a) << endl; } }
编译正确通过
更通用的实现
通用写法用于支持多类型的情况,例如需要判断某个类实现的newVersionInterface函数是否支持某个类型的参数
实现的逻辑比较简单,不做解释了
1 2 3 4 5 6 7 8 9 10 11 12 template <typename ...>using void_t_cpp11 = void ;template <template <typename ...> class Expression , typename DefaultT, typename ... Args>struct is_detected_impl : std::false_type {};template <template <typename ...> class Expression , typename ... Args>struct is_detected_impl <Expression, void_t_cpp11<Expression<Args...>>, Args...> : std::true_type {}; template <template <typename ...> class Expression , typename ... Args>constexpr bool is_detected = is_detected_impl<Expression, void , Args...>::value;
这里使用is_detected来默认给is_detected_impl传入第二个为void类型,从而匹配到偏特化中的void_t_cpp11类型
在实现了通用模板以后,就可以实现具体的newVersionInterface了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T, typename U>using isNewVersionExpression = decltype (std::declval <T>().newVersionInterface (std::declval <U>()));template <typename T, typename std::enable_if<is_detected<isNewVersionExpression, T, int >>::type* = nullptr > void newVersionInterface (T &t, int a) { t.newVersionInterface (a); static_assert (sizeof (t) == 0 , "newVersionInterface found" ); } template <typename T, typename std::enable_if<!is_detected<isNewVersionExpression, T, int >>::type* = nullptr > void newVersionInterface (T &t, int ) { static_assert (sizeof (t) == 0 , "newVersionInterface not found" ); }
最后是使用newVersionInterface的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct A { void newVersionInterface (int ) ; }; struct B { void newVersionInterface (void ) ; }; struct C {}; int main () { A a; newVersionInterface (a, 0 ); B b; newVersionInterface (b, 0 ); C c; newVersionInterface (c, 0 ); }
编译打印报错来验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /root/cpp_test/temp32.cpp: In instantiation of ‘void newVersionInterface (T&, int ) [with T = A; typename std::enable_if<is_detected<isNewVersionExpression, T, int > >::type* <anonymous> = 0 ]’: /root/cpp_test/temp32.cpp:45 :29 : required from here /root/cpp_test/temp32.cpp:23 :29 : error: static assertion failed: newVersionInterface found 23 | static_assert (sizeof (t) == 0 , "newVersionInterface found" ); | ~~~~~~~~~~^~~~ /root/cpp_test/temp32.cpp: In instantiation of ‘void newVersionInterface (T&, int ) [with T = B; typename std::enable_if<(! is_detected<isNewVersionExpression, T, int >)>::type* <anonymous> = 0 ]’: /root/cpp_test/temp32.cpp:47 :29 : required from here /root/cpp_test/temp32.cpp:28 :29 : error: static assertion failed: newVersionInterface not found 28 | static_assert (sizeof (t) == 0 , "newVersionInterface not found" ); | ~~~~~~~~~~^~~~ /root/cpp_test/temp32.cpp: In instantiation of ‘void newVersionInterface (T&, int ) [with T = C; typename std::enable_if<(! is_detected<isNewVersionExpression, T, int >)>::type* <anonymous> = 0 ]’: /root/cpp_test/temp32.cpp:49 :29 : required from here /root/cpp_test/temp32.cpp:28 :29 : error: static assertion failed: newVersionInterface not found
编译的报错信息显示
A实现的newVersionInterface方法,提供了int类型参数,找到了预期方法
B实现的newVersionInterface方法提供了void类型,所以没找到
C没有实现newVersionInterface方法,所以没找到
类内模板全特化
1 2 3 4 5 6 7 struct Test { template <bool > struct SubTest {}; template <> struct SubTest <true > {}; };
在C++17以前,模板的全特化不能在类内进行,标准可见C++11的n3376-14.7.3/2 :
An explicit specialization shall be declared in a namespace enclosing
the specialized template
在C++17以后可以,标准可见C++17的17.7.3/2
:
An explicit specialization may be declared in any scope in which the
corresponding primary template may be defined
C++17的验证可以在clangd
18.1.0 上成功编译
但是在gcc
14.1 会编译失败,应该是gcc的bug ,报错:
1 2 3 /root/cpp_test/temp33.cpp:5:15: error: explicit specialization in non-namespace scope ‘struct Test’ 5 | template <> | ^
很多时候全特化的模板类不希望暴露到类外,所以对于主要平台是gcc的我来说,必须绕过这个问题
思路就是使用偏特化代替全特化
1 2 3 4 5 6 7 struct Test { template <bool , typename = void > struct SubTest {}; template <typename dummy> struct SubTest <true , dummy> {}; };
lambda装饰器:给需要运行的函数添加固定逻辑
这是一个很常见的需求,例如在服务端的rpc接口内打印接口名,耗时,最重要的是try,catch异常
在C++11以前的凑活方案
往往只能使用RAII的方式在rpc接口的声明变量来打印耗时和返回值,然后使用一个__TRY__
,__CATCH__
宏来简化复杂的try,
catch
伪代码大概如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 struct ScopeLog { ScopeLog (const string &fileName) : fileName_ (fileName), startTimeMs_ (getNowMs ()){} ~ScopeLog () { instantLog (LOG); } void instantLog ( std::ostream &os ) { ... os << "cost: " << getNowMs () - startTimeMs_; os << endl; } string fileName_; int64_t startTimeMs_; }; #define __TRY__ try{ #define __CATCH__ }catch(exception& ex) \ { \ log << "exception: " << ex.what() << endl; \ } \ catch(...) \ { \ log << "unknown exception" << endl; \ } struct MyServer { int myInterface (const Req &req, Rsp &rsp) override { int ret = 0 ; ScopeLog log; log << __FUNCTION__ << "|" << req << "|" << rsp << "|" << ret; __TRY__ ... __CATCH__ return ret; } };
可以看到,myInterface需要编写大量重复性很高也很容易出错的代码
从C++11开始,有缺陷的宏解决方案
C++11开始的lambda结合宏可以更好的解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <exception> #include <iostream> template <typename FuncType>const FuncType& make_function_wrapper (const FuncType &func) { return func; } #define TryCatchFunc(...) \ do {\ auto &f = make_function_wrapper(__VA_ARGS__);\ try {\ f();\ }catch (std::exception &e) {\ std::cout << e.what() << std::endl;\ }catch (...) {\ std::cout << "unknown exception" << std::endl;\ }\ \ }while(0); int main () { int ret = 0 ; TryCatchFunc ([=, &ret]() { ret = 1 ; }); std::cout << ret << std::endl; }
这里必须使用宏可变参数传递给make_function_wrapper
这个模板函数来捕获传入的lambda参数,这是因为一旦lambda出现逗号,例如这里的[=, &ret]
宏会将其识别成多个参数,从而出现报错
例如改成这样
1 2 3 4 5 6 7 8 9 10 #define WrongTryCatchFunc(f) \ do {\ try {\ f();\ }catch (std::exception &e) {\ std::cout << e.what() << std::endl;\ }catch (...) {\ std::cout << "unknown exception" << std::endl;\ }\ }while(0);
报错
1 2 /root/cpp_test/temp.cpp:41:6: error: macro "WrongTryCatchFunc" passed 2 arguments, but takes just 1 41 | });
myInterface的伪代码使用TryCatchFunc来改写一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #define ServerTryCatchFunc(...) \ do {\ auto now = getNowMs();\ auto &f = make_function_wrapper(__VA_ARGS__);\ try {\ int ret = f();\ }catch (std::exception &e) {\ LOG << __FUNCTION__ << "|" << \ req << "|" << e.what() << "|cost: " << getNowMs() - now << endl;\ }catch (...) {\ LOG << __FUNCTION__ << "|" << req << \ "|unknown exception" << "|cost: " << getNowMs() - now << endl;\ }\ LOG << __FUNCTION__ << "|" << req << \ "|" << rsp << "|" << ret << "|cost: " << getNowMs() - now << endl; return ret; }while (0 ); struct MyServer { int myInterface (const Req &req, Rsp &rsp) override { ServerTryCatchFunc ([&]() { int ret = 0 ; ... return ret; }); } };
可以看到,MyServer的实现简单了很多,大致能满足需求了
但是TryCatchFunc本身作为宏,在这个需求下适用,但是作为通用需求,还是很容易踩到预期外的坑
更重要的是,这也不能称之为lambda装饰器,只能调用无参数的lambda而已
正道还是需要使用模板来实现的
从C++11开始的模板解决方案
C++17的apply
C++17开始引入了std::apply函数 ,可以很方便的解决这个问题
1 2 template < class F, class Tuple >constexpr decltype (auto ) apply ( F&& f, Tuple&& t ) ;
传入一个函数f和std::tuple的t,会将t拆解成参数交给函数f来调用
所以设计一个模板参数,将变长参数列表打包成tuple后交给apply使用即可
另外有返回值和没有返回值的实现是不一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <exception> #include <iostream> #include <tuple> template <typename FuncType, typename ... ArgsType, typename retType = decltype (std::apply (std::declval <FuncType>(), std::declval<std::tuple<ArgsType&&...>>())), typename std::enable_if<std::is_void<retType>::value>::type * = nullptr > void tryCatchFunc (const FuncType& func, ArgsType&&... args) { auto args_tuple = std::forward_as_tuple(args...); try { std::apply (func, args_tuple); }catch (std::exception &e) { std::cout << e.what () << std::endl; }catch (...) { std::cout << "unknown exception" << std::endl; } } template <typename FuncType, typename ... ArgsType, typename retType = decltype (std::apply (std::declval <FuncType>(), std::declval<std::tuple<ArgsType&&...>>())), typename std::enable_if<!std::is_void<retType>::value>::type * = nullptr > retType tryCatchFunc (const FuncType& func, ArgsType&&... args) { auto args_tuple = std::forward_as_tuple(args...); retType ret; try { ret = std::apply (func, args_tuple); }catch (std::exception &e) { std::cout << e.what () << std::endl; }catch (...) { std::cout << "unknown exception" << std::endl; } return ret; } int main () { int v = 0 ; int returnV = tryCatchFunc ([=, &v](int value) { v = value; return v; }, 2 ); std::cout << v << std::endl; std::cout << returnV << std::endl; }
这里还有一个小瑕疵,就是__FUNCTION__
只会获取到lambda的operator()
,所以需要一个小技巧来获取当前的函数名
getFuncName模板函数获取当前函数名
使用cxxabi可以获取到类型名称,lambda的类型名称里面包含了定义这个函数的名称
所以首先实现getType模板函数获取lambda的类型名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <cxxabi.h> #include <memory> #include <string> inline std::string demangle (const char * mangled) { int status; std::unique_ptr<char [], void (*) (void *) > result ( abi::__cxa_demangle(mangled, 0 , 0 , &status), std::free) ; return result.get () ? std::string (result.get ()) : "error occurred" ; } template <typename T>const std::string& getType () { static std::string type = demangle (typeid (T).name ()); return type; }
对于刚才的例子,getType<FuncType>
将返回main::{lambda(int)#2}
其中::
是作用域,最后一个总是{lambda}
,倒数第二个就是真正的函数名
所以getFuncName模板函数做一个split操作,就可以获取到函数名了,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <vector> std::vector<std::string> split (const std::string& s, const std::string& delimiter) { std::vector<std::string> tokens; std::string token; size_t prev = 0 , pos = 0 ; do { pos = s.find (delimiter, prev); if (pos == std::string::npos) pos = s.length (); token = s.substr (prev, pos - prev); if (!token.empty ()) tokens.push_back (token); prev = pos + delimiter.length (); } while (pos < s.length () && prev < s.length ()); return tokens; } template <typename T>const std::string& getFuncName () { static std::string func = split (getType <T>(), "::" ).size () >= 2 ? split (getType <T>(), "::" )[split (getType <T>(), "::" ).size () - 2 ] : "" ; return func; }
修改下tryCatchFunc,使用g++ -std=c++17 -o out.exe -g -Wall -lunwind temp.cpp
重新编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 template <typename FuncType, typename ... ArgsType, typename retType = decltype (std::apply (std::declval <FuncType>(), std::declval<std::tuple<ArgsType&&...>>())), typename std::enable_if<std::is_void<retType>::value>::type * = nullptr > void tryCatchFunc (const FuncType& func, ArgsType&&... args) { auto args_tuple = std::make_tuple (args...); try { std::apply (func, args_tuple); }catch (std::exception &e) { std::cout << e.what () << std::endl; }catch (...) { std::cout << "unknown exception" << std::endl; } std::cout << getFuncName <FuncType>() << std::endl; } template <typename FuncType, typename ... ArgsType, typename retType = decltype (std::apply (std::declval <FuncType>(), std::declval<std::tuple<ArgsType&&...>>())), typename std::enable_if<!std::is_void<retType>::value>::type * = nullptr > retType tryCatchFunc (const FuncType& func, ArgsType&&... args) { auto args_tuple = std::make_tuple (args...); retType ret; try { ret = std::apply (func, args_tuple); }catch (std::exception &e) { std::cout << e.what () << std::endl; }catch (...) { std::cout << "unknown exception" << std::endl; } std::cout << getFuncName <FuncType>() << std::endl; return ret; }
C++11自己实现apply
祖传代码怎么使用std::apply
呢?那就只能自己实现辣
从cpprefrerence的std::apply 上可以得到提示
1 2 3 4 5 6 template <class F,class Tuple, std::size_t ... I>constexpr decltype (auto ) apply-impl (F&& f, Tuple&& t, std::index_sequence<I...>) { return INVOKE (std::forward<F>(f), std::get <I>(std::forward<Tuple>(t))...); }
apply的实现是获取当前的参数个数,生成序列号,例如3个参数就是0, 1,
2
然后通过可变参数列表展开,std::get<0>(tuple),std::get<1>(tuple),std::get<2>(tuple),分别获取到参数再传递给func即可
在C++11没有std::index_sequence(C++14开始有),因此也需要自己实现一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 template <int ...data>struct my_index_sequence { template <int N> using push_back = my_index_sequence<data..., N>; }; template <int L, int R, typename = void >struct my_make_index_sequence { using ret = typename my_make_index_sequence<L, R - 1 >::ret ::template push_back<R>; }; template <int L, int R>struct my_make_index_sequence <L, R, typename std::enable_if<L == R>::type> { using ret = my_index_sequence<L>; };
接下来就是实现my_apply了,由于C++11没有auto返回值(C++14开始有),所以return值看起来复杂了一点,"复读"了一遍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename Func, typename Tuple, int ...S>auto my_apply_imp (Func && func, Tuple && t, my_index_sequence<S...>) -> decltype (std::forward<Func>(func)(std::get<S>(std::forward<Tuple>(t))...)) { return std::forward<Func>(func)(std::get <S>(std::forward<Tuple>(t))...); } template <typename Func, typename Tuple, int size = std::tuple_size<typename std::remove_reference<Tuple>::type>::value - 1 > auto my_apply (Func && func, Tuple && t) -> decltype (my_apply_imp (std::forward<Func>(func), std::forward<Tuple>(t), typename my_make_index_sequence<0 , size>::ret{})) { return my_apply_imp (std::forward<Func>(func), std::forward<Tuple>(t), typename my_make_index_sequence<0 , size>::ret{}); }
my_apply通过tuple_size先获取到了tuple的数量,然后my_make_index_sequence生成一个my_index_sequence序列数类,传入到my_apply_imp
从而让my_apply_imp根据生成的my_index_sequence类,匹配到S序列数
从而让std::get展开为逗号分隔的多个表达式,传递给func
可以看到,实现这个功能,关键在于C++11开始支持的,可变参数列表展开,这个功能不止是宏的_VA_ARGS__
,可以按模式展开,非常强大
小结
在C++11下,完整实现需求的serverTryCatchFunc,给myInterface使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <cxxabi.h> #include <memory> #include <string> #include <vector> inline std::string demangle (const char * mangled) { int status; std::unique_ptr<char [], void (*) (void *) > result ( abi::__cxa_demangle(mangled, 0 , 0 , &status), std::free) ; return result.get () ? std::string (result.get ()) : "error occurred" ; } template <typename T>const std::string& getType () { static std::string type = demangle (typeid (T).name ()); return type; } std::vector<std::string> split (const std::string& s, const std::string& delimiter) { std::vector<std::string> tokens; std::string token; size_t prev = 0 , pos = 0 ; do { pos = s.find (delimiter, prev); if (pos == std::string::npos) pos = s.length (); token = s.substr (prev, pos - prev); if (!token.empty ()) tokens.push_back (token); prev = pos + delimiter.length (); } while (pos < s.length () && prev < s.length ()); return tokens; } template <typename T>const std::string& getFuncName () { static std::string func = split (getType <T>(), "::" ).size () >= 2 ? split (getType <T>(), "::" )[split (getType <T>(), "::" ).size () - 2 ] : "" ; return func; } #include <exception> #include <iostream> #include <tuple> template <typename FuncType, typename ... ArgsType, typename retType = decltype (my_apply (std::declval <FuncType>(), std::declval<std::tuple<ArgsType&&...>>())), typename std::enable_if<std::is_void<retType>::value>::type * = nullptr > void serverTryCatchFunc (const FuncType& func, ArgsType&&... args) { auto now = getNowMs (); auto args_tuple = std::make_tuple (args...); auto & req = get <0 >(args_tuple); auto & rsp = get <1 >(args_tuple); try { my_apply (func, args_tuple); }catch (std::exception &e) { LOG << getFuncName <FuncType>() << "|" << req << "|" << e.what () << "|cost: " << getNowMs () - now << endl; }catch (...) { LOG << getFuncName <FuncType>() << "|" << req << "|unknown exception" << "|cost: " << getNowMs () - now << endl; } LOG << getFuncName <FuncType>() << "|" << req << "|" << rsp << "|cost: " << getNowMs () - now << endl; } template <typename FuncType, typename ... ArgsType, typename retType = decltype (my_apply (std::declval <FuncType>(), std::declval<std::tuple<ArgsType&&...>>())), typename std::enable_if<std::is_void<retType>::value>::type * = nullptr > retType serverTryCatchFunc (const FuncType& func, ArgsType&&... args) { retType ret; auto now = getNowMs (); auto args_tuple = std::make_tuple (args...); auto & req = get <0 >(args_tuple); auto & rsp = get <1 >(args_tuple); try { ret = my_apply (func, args_tuple); }catch (std::exception &e) { LOG << getFuncName <FuncType>() << "|" << req << "|" << e.what () << "|cost: " << getNowMs () - now << endl; }catch (...) { LOG << getFuncName <FuncType>() << "|" << req << "|unknown exception" << "|cost: " << getNowMs () - now << endl; } LOG << getFuncName <FuncType>() << "|" << req << "|" << rsp << "|" << ret << "|cost: " << getNowMs () - now << endl; return ret; } struct MyServer { int myInterface (const Req &req, Rsp &rsp) override { return serverTryCatchFunc ([&](const Req &req, Rsp &rsp) { int ret = 0 ; ... return ret; }, req, rsp); } };
可以看到serverTryCatchFunc支持可变参数列表,即使超过2个参数,也可以正确传递给lambda,只打印关心的req = get<0>
和rsp = get<1>
甚至对serverTryCatchFunc进行微调,通过enable_if参数列表个数,可以支持仅存在req的情况