C++模板SFINAE特性與反射機制

C++模板提供了一個SFINAE(subsitate failure is not an error)的機制(模板匹配失敗不是錯誤)厦画,這是模板里面一個非常有意思的特性,利用這個機制可以檢查一個結(jié)構(gòu)體是否包含某個成員等操作。c++語言本身沒有提供反射機制(也有利用pb實現(xiàn)反射)绞呈,利用SFINAE機制兔毙,可以實現(xiàn)類似于反射的功能柠辞。


在介紹SFINAE之前尘颓,整理一下模板的基本語法走触。

  • 基本語法
    C++模板基本的使用方法,使用關(guān)鍵字template, typename疤苹,可以實現(xiàn)基本泛型函數(shù)或者類互广。這里就不在贅述了。
// 一個簡單的函數(shù)模板例子
template<typename T>
T get_max(T a, T b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << a << " " << b << endl;
    auto result = a > b ? a: b;
    return result;
}

// 提供特化版本卧土,處理某些特定類型
template<>
float get_max(float a, float b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << "float : " << a << " " << b << endl;
    auto result = a > b ? a: b;
    return result;
}

// 提供偏特化版本惫皱,解決傳遞指針的問題
template<typename T>
T get_max(T* a, T* b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << *a << " " << *b << endl;
    auto result = *a > *b ? *a: *b;
    return result;
}

// 默認(rèn)值模板
template<typename T = std::string>
T get_max(string a, string b)  // 默認(rèn)值這里的參數(shù)類型需要直接指定
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << "STRING : " <<a << " " << b << endl;
    return a;
}
  • 關(guān)鍵字decltype的用法
    1)尾置返回類型
    通過decltype指定模板函數(shù)的返回類型
// 尾置返回類型
template<typename T1, typename T2>
auto get_max(T1 a, T2 b) -> decltype(b < a ? a : b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    cout << a << " " << b << endl;
    return a > b ? a: b;
}

2)獲得變量的類型

int a=8, b=3;
auto c = a + b;  //運行時需要實際執(zhí)行a+b,哪怕編譯時就能推導(dǎo)出類型
decltype(a+b) d; //編譯期類型推導(dǎo)
// auto c;  // error // 不可以用auto c; 直接聲明變量尤莺,必須同時初始化旅敷。
  • 模板匹配decay機制
    模板在進行匹配的時候,會進行一個退化颤霎,比如const int 如果找不到對應(yīng)的類型媳谁,會退化為int。其實就是把各種引用啊什么的修飾去掉友酱,把cosnt int&退化為int韩脑。
// 模板類型退化 decay 
template<typename T>
T get_max_decay(T a, T b)
{
    cout << "FUNCTION NAME : " << __FUNCTION__ << ", LINE : " <<__LINE__ << endl;
    return a > b ? a: b;
}

int main()
{
    // 模板類型退化 decay 
    // 看著比較抽象,其實就是把各種引用啊什么的修飾去掉粹污,把cosnt int&退化為int
    int const c = 42;
    int i = 1;
    get_max_decay(i, c); // OK: T 被推斷為 int段多,c 中的 const 被 decay 掉
    get_max_decay(c, c); // OK: T 被推斷為 int

    int& ir = i;
    get_max_decay(i, ir); // OK: T 被推斷為 int, ir 中的引用被 decay 掉

    // ...
}
  • SFINAE 機制
    SFINAE(Substitution Failure Is Not An Error壮吩,替換失敗并非錯誤)這個看著很抽象进苍,實際非常簡單,是一種常見的模板技巧鸭叙。比如觉啊,利用模板的SFINAE判斷一個結(jié)構(gòu)體中是否包含某個成員。
#include <iostream>
#include <type_traits>
using namespace std;

template<typename T>
struct check_has_member_id
{
    // 僅當(dāng)T是一個類類型時沈贝,“U::*”才是存在的杠人,從而這個泛型函數(shù)的實例化才是可行的
    // 否則,就將觸發(fā)SFINAE
    template<typename U>
    static void check(decltype(&U::id)){}

    // // 僅當(dāng)觸發(fā)SFINAE時宋下,編譯器才會“被迫”選擇這個版本
    template<typename U>
    static int check(...){}

    enum {value = std::is_void<decltype(check<T>(NULL))>::value};
};

struct TEST_STRUCT
{
    int rid;
};

struct TEST_STRUCT2
{
    int id;
};

int main()
{
    check_has_member_id<TEST_STRUCT> t1;
    cout << t1.value << endl;

    check_has_member_id<TEST_STRUCT2> t2;
    cout << t2.value << endl;

    check_has_member_id<int> t3;
    cout << t3.value << endl;
    return 0;
}
// g++ --std=c++11  xxx.c

核心的代碼是在實例化check_has_member_id對象的時候嗡善,通過模板參數(shù)T的類型,決定了結(jié)構(gòu)體中對象value的值学歧。而value的值是通過check<T>函數(shù)的返回值是否是void決定的罩引。如果T中含有id成員的話,那么就會匹配第一個實例枝笨,返回void袁铐;如果不包含id的話揭蜒,會匹配默認(rèn)的實例,返回int剔桨。

利用這個機制還可以做很多類似的判斷屉更,比如判斷一個類是否是結(jié)構(gòu)體。

#include <iostream>
#include <type_traits>

// 2. 判斷變量是否是一個struct 或者 類
// http://www.reibang.com/p/d09373b83f86
template <typename T>
struct check
{
    template <typename U>
    static void check_class(int U::*) {}

    template <typename U>
    static int check_class(...) {}

    enum { value = std::is_void<decltype(check_class<T>(0))>::value };
};

class myclass {};

int main()
{
    check<myclass> t;
    std::cout << t.value << std::endl;

    check<int> t2;
    std::cout << t2.value << std::endl;
    return 0;
}
  • enable_if 語法

如果T是一個int類型洒缀,那么返回值是bool類型瑰谜。如果不是int的話,就匹配不到找個實例帝洪。使用enable_if的好處是控制函數(shù)只接受某些類型的(value==true)的參數(shù),否則編譯報錯脚猾。

// 判斷class T 是否有某個成員函數(shù)
// enable if 
// enable_if example: two ways of using enable_if
#include <iostream>
#include <type_traits>

// 1. the return type (bool) is only valid if T is an integral type:
template <class T>
typename std::enable_if<std::is_integral<T>::value,bool>::type
  is_odd (T i) {return bool(i%2);}

int main() 
{
    short int i = 1;    // code does not compile if type of i is not integral
    std::cout << "i is odd: " << is_odd(i) << std::endl;

    // 編譯錯誤
    // double j = 10.0;
    // std::cout << "i is odd: " << is_odd(j) << std::endl;

    return 0;
}

利用模板的這種機制葱峡,可以設(shè)計“通用”函數(shù)接口。比如龙助,如果work_func<T>(T data) 函數(shù)傳入的類型T中包含了T::is_print成員就打印“xxxx”砰奕,如果不包含就打印“yyy”。


這個事情除了可以用模板來做提鸟,利用反射也可以實現(xiàn)军援。c++本身沒有提供反射,利用pb称勋,也可以實現(xiàn)胸哥。下面介紹一下基于pb實現(xiàn)的c++反射的機制。

int get_feature(const HeartBeatMessage& hb_msg, const std::string& name) 
{
    const google::protobuf::Descriptor* des = hb_msg.GetDescriptor();
    const google::protobuf::FieldDescriptor* fdes = des->FindFieldByName(name);
    assert(fdes != nullptr);
    const google::protobuf::Reflection* ref = hb_msg.GetReflection();
    cout << ref->GetString(hb_msg, fdes) << endl;
    return 0;
}

int main()
{
    HeartBeatMessage msg;
    fstream input("./heartbeat.db",ios::in|ios::binary);
    if(!msg.ParseFromIstream(&input))
    {
        cerr << "read data from file error." << endl;
        return -1;
    }
    
    // msg 是pb的msg對象赡鲜,從msg對象里面找到對應(yīng)“hostname”字段 
    get_feature(msg, "hostName");
}

todo ...


參考:
https://jguegant.github.io/blogs/tech/sfinae-introduction.html
https://my.oschina.net/u/4051725/blog/4458011
https://izualzhy.cn/SFINAE-and-enable_if

stackoverflow上面有很多大佬balalala寫了很多空厌,延伸閱讀:
https://stackoverflow.com/questions/257288/templated-check-for-the-existence-of-a-class-member-function

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市银酬,隨后出現(xiàn)的幾起案子嘲更,更是在濱河造成了極大的恐慌,老刑警劉巖揩瞪,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赋朦,死亡現(xiàn)場離奇詭異,居然都是意外死亡李破,警方通過查閱死者的電腦和手機宠哄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗤攻,“玉大人琳拨,你說我怎么就攤上這事⊥筒埽” “怎么了狱庇?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵惊畏,是天一觀的道長。 經(jīng)常有香客問我密任,道長颜启,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任浪讳,我火速辦了婚禮缰盏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淹遵。我一直安慰自己口猜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布透揣。 她就那樣靜靜地躺著济炎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辐真。 梳的紋絲不亂的頭發(fā)上须尚,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音侍咱,去河邊找鬼耐床。 笑死,一個胖子當(dāng)著我的面吹牛楔脯,可吹牛的內(nèi)容都是我干的撩轰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼昧廷,長吁一口氣:“原來是場噩夢啊……” “哼钧敞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起麸粮,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤溉苛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弄诲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愚战,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年齐遵,在試婚紗的時候發(fā)現(xiàn)自己被綠了寂玲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡梗摇,死狀恐怖拓哟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伶授,我是刑警寧澤断序,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布流纹,位于F島的核電站,受9級特大地震影響违诗,放射性物質(zhì)發(fā)生泄漏漱凝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一诸迟、第九天 我趴在偏房一處隱蔽的房頂上張望茸炒。 院中可真熱鬧,春花似錦阵苇、人聲如沸壁公。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽紊册。三九已至,卻和暖如春趁怔,著一層夾襖步出監(jiān)牢的瞬間湿硝,已是汗流浹背薪前。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工润努, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人示括。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓铺浇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垛膝。 傳聞我的和親對象是個殘疾皇子鳍侣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 夜鶯2517閱讀 127,712評論 1 9
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭吼拥,有人歡樂有人憂愁倚聚,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,523評論 28 53
  • 兔子雖然是枚小碩 但學(xué)校的碩士四人寢不夠 就被分到了博士樓里 兩人一間 在學(xué)校的最西邊 靠山 兔子的室友身體不好 ...
    待業(yè)的兔子閱讀 2,586評論 2 9
  • 信任包括信任自己和信任他人 很多時候凿可,很多事情惑折,失敗、遺憾枯跑、錯過惨驶,源于不自信,不信任他人 覺得自己做不成敛助,別人做不...
    吳氵晃閱讀 6,181評論 4 8