從一個例子看現代C++的威力
引子
最近準備重構一下我的kapok庫,讓meta函數可以返回元素為kv的tuple,例如:
struct person { std::string name; int age; META(name, age) //定義一個支持變參的meta函數 }; int main() { person p = {“tom”, 20}; auto tp = p.meta(); static_assert(std::is_same(std::tuple<std::pair<std::string, int>>, decltype(tp), “not same”); //tp在內存中是這樣的 {{“name”:”tom”}, {“age”:20}}; return 0; }
類似這個META的實現我在msgpack的庫里看到了,在這里:
僅僅是宏元的代碼就數百行了,看起來非常復雜,msgpack之所以用這么復雜的方式去實現恐怕是為了支持c++98/03標準。本來想看看msgpack是如何實現META函數的,但是它的宏元代碼讀起來比較困難,于是作罷。
后來想起群里的ddrm實現了類似的功能,據說沒有msgpack這么復雜,簡潔一些,于是向ddrm要來了代碼(在此感謝ddrm分享的源碼)。他的代碼也是用宏元,但是比msgpack的代碼少很多,將近一百行代碼。不過,我不太喜歡這么復雜的代碼,我希望用一種更簡單的方式去實現這個效果。這里附上ddrm的代碼,大家可以借鑒參考一下。
#ifndef TUPLE_MACRO_DEF_Hdefine TUPLE_MACRO_DEF_H
define MARCO_EXPAND(arg_list) arg_list
define APPLY_VARIADIC_MACRO(macro,tuple) macro tuple
define ADD_REFERENCE(t) std::reference_wrapper<decltype(t)>(t)
define ADD_REFERENCE_CONST(t) std::reference_wrapper<std::add_const_t<decltype(t)>>(t)
define PAIR_OBJECT(t) std::make_pair(#t, ADD_REFERENCE(t))
define PAIR_OBJECT_CONST(t) std::make_pair(#t, ADD_REFERENCE_CONST(t))
define MAKE_TUPLE(...) auto tuple() { return std::make_tuple(VA_ARGS); }
define MAKE_TUPLE_CONST(...) auto tuple() const { return std::make_tuple(VA_ARGS); }
/ arg list expand macro, now support 40 args /
define MAKE_ARG_LIST_1(op, arg, ...) op(arg)
define MAKE_ARG_LIST_2(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_1(op, VA_ARGS))
define MAKE_ARG_LIST_3(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_2(op, VA_ARGS))
define MAKE_ARG_LIST_4(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_3(op, VA_ARGS))
define MAKE_ARG_LIST_5(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_4(op, VA_ARGS))
define MAKE_ARG_LIST_6(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_5(op, VA_ARGS))
define MAKE_ARG_LIST_7(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_6(op, VA_ARGS))
define MAKE_ARG_LIST_8(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_7(op, VA_ARGS))
define MAKE_ARG_LIST_9(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_8(op, VA_ARGS))
define MAKE_ARG_LIST_10(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_9(op, VA_ARGS))
define MAKE_ARG_LIST_11(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_10(op, VA_ARGS))
define MAKE_ARG_LIST_12(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_11(op, VA_ARGS))
define MAKE_ARG_LIST_13(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_12(op, VA_ARGS))
define MAKE_ARG_LIST_14(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_13(op, VA_ARGS))
define MAKE_ARG_LIST_15(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_14(op, VA_ARGS))
define MAKE_ARG_LIST_16(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_15(op, VA_ARGS))
define MAKE_ARG_LIST_17(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_16(op, VA_ARGS))
define MAKE_ARG_LIST_18(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_17(op, VA_ARGS))
define MAKE_ARG_LIST_19(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_18(op, VA_ARGS))
define MAKE_ARG_LIST_20(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_19(op, VA_ARGS))
define MAKE_ARG_LIST_21(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_20(op, VA_ARGS))
define MAKE_ARG_LIST_22(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_21(op, VA_ARGS))
define MAKE_ARG_LIST_23(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_22(op, VA_ARGS))
define MAKE_ARG_LIST_24(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_23(op, VA_ARGS))
define MAKE_ARG_LIST_25(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_24(op, VA_ARGS))
define MAKE_ARG_LIST_26(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_25(op, VA_ARGS))
define MAKE_ARG_LIST_27(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_26(op, VA_ARGS))
define MAKE_ARG_LIST_28(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_27(op, VA_ARGS))
define MAKE_ARG_LIST_29(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_28(op, VA_ARGS))
define MAKE_ARG_LIST_30(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_29(op, VA_ARGS))
define MAKE_ARG_LIST_31(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_30(op, VA_ARGS))
define MAKE_ARG_LIST_32(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_31(op, VA_ARGS))
define MAKE_ARG_LIST_33(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_32(op, VA_ARGS))
define MAKE_ARG_LIST_34(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_33(op, VA_ARGS))
define MAKE_ARG_LIST_35(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_34(op, VA_ARGS))
define MAKE_ARG_LIST_36(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_35(op, VA_ARGS))
define MAKE_ARG_LIST_37(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_36(op, VA_ARGS))
define MAKE_ARG_LIST_38(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_37(op, VA_ARGS))
define MAKE_ARG_LIST_39(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_38(op, VA_ARGS))
define MAKE_ARG_LIST_40(op, arg, ...) op(arg), MARCO_EXPAND(MAKE_ARG_LIST_39(op, VA_ARGS))
/ emmbed marco, using EMMBED_TUPLE(5 , a, b, c, d, e) / //note use MACROCONCAT like A####B direct may cause marco expand error
define MACRO_CONCAT(A, B) MACRO_CONCAT1(A, B)
define MACROCONCAT1(A, B) A####B
define MAKE_ARG_LIST(N, op, arg, ...) \
MACRO_CONCAT(MAKE_ARG_LIST, N)(op, arg, __VA_ARGS__)
define EMMBED_TUPLE(N, ...) \
MAKE_TUPLE(MAKE_ARG_LIST(N, PAIR_OBJECT, VA_ARGS)) \ MAKE_TUPLE_CONST(MAKE_ARG_LIST(N, PAIR_OBJECT_CONST, VA_ARGS))
// see http://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5
define RSEQ_N() \
63,62,61,60, \ 59,58,57,56,55,54,53,52,51,50, \ 49,48,47,46,45,44,43,42,41,40, \ 39,38,37,36,35,34,33,32,31,30, \ 29,28,27,26,25,24,23,22,21,20, \ 19,18,17,16,15,14,13,12,11,10, \ 9,8,7,6,5,4,3,2,1,0
define ARG_N( \
_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,N, ...) N
define GET_ARG_COUNT(...) APPLY_VARIADIC_MACRO(GET_ARG_COUNT_INNER,(VA_ARGS, RSEQ_N()))
define GET_ARG_COUNT_INNER(...) APPLY_VARIADIC_MACRO(ARG_N, (VA_ARGS))
define EMMBED(...) EMMBED_TUPLE(GET_ARG_COUNT(VA_ARGS), VA_ARGS)
endif</pre>
10行代碼解決問題
我探索了一些可能的方案后,最終找到了一種滿意的方案,最終的實現代碼不過十來行,非常簡潔,感謝c++11/14,正是現代C++才讓代碼變得如此簡潔。
實現思路也比較簡單,問題的本質是將輸入的參數值變成一個pair,key是值的字面量,value是變量本身的值,所以生成的pair是這樣的std::pair< std::string, someval >, 另外這個pair是每一個輸入參數值都會生成一個pair,如果是一系列的參數值就要生成一系列的pair,這就要求支持變參。所以問題的關鍵是要在展開變參的過程中將這些pair放到一個tuple中,這對于c++11/14來說是不難辦到的,下面是實現代碼:
#define MAKE_PAIR(text) std::pair<std::string, decltype(text)>{#text, text} template<typename T> constexpr static inline auto apply(T const & args) { return args; }template<typename T, typename T1, typename... Args> constexpr static inline auto apply(T const & t, const T1& first, const Args&... args) { return apply(std::tuple_cat(t, std::make_tuple(MAKE_PAIR(first))), args...); }
define META(...) auto meta(){ return apply(std::tuple<>(), VA_ARGS); }</pre>
使用現代C++之后,僅僅需要10行代碼就可以實現之前需要上百行甚至數百行代碼才能實現的目標,這無疑體現了現代C++的巨大威力。除了非常簡潔的優點之外,還解決了一個宏元無法徹底解決的問題,宏元需要預先定義一些參數的宏,這些宏定義是有限的,如果參數超出定義的上限就會編譯報錯,而這個變參版本完全不用擔心這個問題,支持任意個參數。
后記
這件事給了我一個啟示,如果我們一直按照前人的路去走,就很難超越前人,如果我們運用新的技術,改變思路,常常會化繁為簡,化腐朽為神奇。只有通過新技術、新思維去創造、創新才能超越前人。
作者簡介:
祁宇,珠海云創科技研發中心技術總監,《深入應用C++11》作者,C++開源社區purecpp.org創始人,致力于C++11的應用、研究和推廣。樂于研究和分享技術,愛好C++,愛好開源。
第一時間掌握最新移動開發相關信息和技術,請關注mobilehub公眾微信號(ID: mobilehub)。