從一個例子看現代C++的威力

aaanly 8年前發布 | 32K 次閱讀 kapok C/C++開發 C/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_H

define 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)。


 本文由用戶 aaanly 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!