C++11 標(biāo)準(zhǔn)庫源代碼剖析:連載之七

std::tuple

tuple簡史

C++ Reference對(duì)tuple的解釋是“fixed-size collection of heterogeneous values”额划,也就是有固定長度的異構(gòu)數(shù)據(jù)的集合妙啃。每一個(gè)C++代碼仔都很熟悉的std::pair就是一種tuple。但是std::pair只能容納兩個(gè)數(shù)據(jù)俊戳,而C++11標(biāo)準(zhǔn)庫中定義的tuple可以容納任意多個(gè)揖赴、任意類型的數(shù)據(jù)。

tuple的用法

C++ 11標(biāo)準(zhǔn)庫中的tuple是一個(gè)模板類抑胎,使用時(shí)需要包含頭文件<tuple>

#include <tuple>

using tuple_type = std::tuple<int, double, char>;
tuple_type t1(1, 2.0, 'a');

不過我們一般都用std::make_tuple函數(shù)來創(chuàng)建一個(gè)tuple燥滑,使用std::make_tuple的好處是不需要指定tuple參數(shù)的類型,編譯器會(huì)自己推斷:

#include <iostream>
#include <tuple>

auto t1 = std::make_tuple(1, 2.0, 'a');
std::cout << typeid(t1).name() << std::endl

可以使用std::get函數(shù)取出tuple中的數(shù)據(jù):

auto t = std::make_tuple(1, 2.0, 'a');
std::cout << std::get<0>(t) << ", " << std::get<1>(t) << ", " << std::get<2>(t) << std::endl; // 1, 2.0, a

C++ 11標(biāo)準(zhǔn)庫中還定義了一些輔助類阿逃,方便我們?nèi)〉靡粋€(gè)tuple類的信息:

using tuple_type = std::tuple<int, double, char>;

// tuple_size: 在編譯期獲得tuple元素個(gè)數(shù)
cout << std::tuple_size<tuple_type>::value << endl; // 3

// tuple_element: 在編譯期獲得tuple的元素類型
cout << typeid(std::tuple_element<2, tuple_type>::type).name() << endl; // c

關(guān)于tuple的用法就簡要介紹到這里铭拧,C++ Reference上有關(guān)于std::tuple的詳細(xì)介紹赃蛛,感興趣的同學(xué)可以去看看。下面我們著重講一下tuple的實(shí)現(xiàn)原理搀菩。

tuple的實(shí)現(xiàn)原理

如果你對(duì)boost::tuple有所了解的話焊虏,應(yīng)該知道boost::tuple是使用遞歸嵌套實(shí)現(xiàn)的,這也是大多數(shù)類庫--比如Loki和 MS VC--實(shí)現(xiàn)tuple的方法秕磷。而libc++另辟蹊徑诵闭,采用了多重繼承的手法實(shí)現(xiàn)。libc++tuple的源代碼極其復(fù)雜澎嚣,大量使用了元編程技巧疏尿,如果我一行行解讀這些源代碼,那本章就會(huì)變成C++模板元編程入門易桃。為了讓你有繼續(xù)看下去的勇氣褥琐,我將libc++ tuple的源代碼簡化,實(shí)現(xiàn)了一個(gè)極簡版tuple晤郑,希望能幫助你理解tuple的工作原理敌呈。

tuple_size

我們先從輔助類開始:

// forward declaration
template<class ...T> class tuple;

template<class ...T> class tuple_size;

// 針對(duì)tuple類型的特化
template<class ...T>
class tuple_size<tuple<T...> > : public std::integral_constant<size_t, sizeof...(T)> {};

這個(gè)比較好理解,如果tuple_size作用于一個(gè)tuple造寝,則tuple_size的值就是sizeof...(T)的值磕洪。所以你可以這樣寫:

cout << tuple_size<tuple<int, double, char> >::value << endl;    // 3

tuple_types

下一個(gè)輔助類就是tuple_types

template<class ...T> struct tuple_types{};

template<class T, size_t End = tuple_size<T>::value, size_t Start = 0>
struct make_tuple_types {};

template<class ...T, size_t End>
struct make_tuple_types<tuple<T...>, End, 0> {
    typedef tuple_types<T...> type;
};

template<class ...T, size_t End>
struct make_tuple_types<tuple_types<T...>, End, 0> {
        typedef tuple_types<T...> type;
};
    

這個(gè)簡化版的typle_types并不做具體的事,就是純粹的類型定義诫龙。需要說明的是析显,如果你要使用這個(gè)簡化版的tuple_types,最好保證End == sizeof...(T) - 1签赃,否則有可能編譯器會(huì)報(bào)錯(cuò)谷异。

type_indices

下面這個(gè)有點(diǎn)復(fù)雜:

template<size_t ...value> struct tuple_indices {};

template<class IndexType, IndexType ...values>
struct integer_sequence {
    template<size_t Start>
    using to_tuple_indices = tuple_indices<(values + Start)...>;
};

template<size_t End, size_t Start>
using make_indices_imp = typename __make_integer_seq<integer_sequence, size_t, End - Start>::template to_tuple_indices<Start>;

template<size_t End, size_t Start = 0>
struct make_tuple_indices {
    typedef make_indices_imp<End, Start> type;
};

__make_integer_seq是LLVM編譯器的一個(gè)內(nèi)置的函數(shù),它的作用--顧名思義--是在編譯期生成一個(gè)序列锦聊,如果你寫下這樣的代碼:

__mkae_integer_seq<integer_sequence, size_t, 3>

則編譯器會(huì)將它展開成:

integer_sequence<0>, integer_sequence<1>, integer_sequence<2>

所以歹嘹,對(duì)于下面的代碼:

make_tuple_indices<3>

編譯器最終會(huì)展開成:

tuple_indices<0>, tuple_indices<1>, tuple_indices<2>

這樣就定義了一個(gè)tuple的索引。

tuple_element

最后一個(gè)輔助類是tuple_element

namespace indexer_detail {
    template<size_t Index, class T>
    struct indexed {
        using type = T;
    };
        
    template<class Types, class Indexes> struct indexer;
        
    template<class ...Types, size_t ...Index>
    struct indexer<tuple_types<Types...>, tuple_indices<Index...> > : public indexed<Index, Types>... {};
        
    template<size_t Index, class T>
    indexed<Index, T> at_index(indexed<Index, T> const&);
} // namespace indexer_detail
    
template<size_t Index, class ...Types>
using type_pack_element = typename decltype(indexer_detail::at_index<Index>(
    indexer_detail::indexer<tuple_types<Types...>,
    typename make_tuple_indices<sizeof...(Types)>::type>{}))::type;
    
template<size_t Index, class ...T>
struct tuple_element<Index, tuple_types<T...> > {
    typedef type_pack_element<Index, T...> type;
};
    
template<size_t Index, class ...T>
struct tuple_element<Index, tuple<T...> > {
    typedef type_pack_element<Index, T...> type;
};

我知道上面的代碼又讓你頭暈?zāi)垦?淄ィ晕視?huì)詳細(xì)解釋一下尺上。如果你寫下這樣的代碼:

tuple_element<1, tuple<int, double, char> >::type

編譯器會(huì)展開成(省略那些煩人的namespace限定符后):tuple_pack_element<1, int, double, char>,進(jìn)而展開成

decltype(
    at_index<1>(indexer<tuple_types<int, double, char>, tuple_indices<3>>{})
)

注意史飞,上面的代碼中定義了類indexer作為函數(shù)at_index的參數(shù)尖昏,而函數(shù)at_index只接受at_index類型的參數(shù),于是編譯器會(huì)來個(gè)向上轉(zhuǎn)型构资,將indexer向上轉(zhuǎn)型成indexed<1,double>(仔細(xì)想想為什么抽诉?),而indexed<1, double>::type就是double吐绵。

看似很復(fù)雜迹淌,其實(shí)無非就是文字代換而已河绽。

tuple

好了,酒水備齊了唉窃,下面上主菜:

template<size_t Index, class Head>
class tuple_leaf {
    Head value;

public:
    tuple_leaf() : vlaue(){}
    
    template<class T>
    explicit tuple_leaf(cosnt T& t) : value(t){}
    
    Head& get(){return value;}
    const Head& get() const {return value;}
};

tuple_leaftuple的基本組成單位耙饰,每一個(gè)tuple_leaf都保存了一個(gè)索引(就是第一個(gè)模板參數(shù)),同時(shí)還有值纹份。

繼續(xù)看:

template<class Index, class ...T> struct tuple_imp;

template<size_t ...Index, class ...T>
struct tuple_imp<tuple_indices<Index...>, T...> : 
    public tuple_leaf<Index, T>... {
    
    tuple_imp(){}
    
    template<size_t ...Uf, class ...Tf, class ...U>
    tuple_imp(tuple_indices<Uf...>, tuple_types<Tf...>, U&& ...u) 
        : tuple_leaf<Uf, Tf>(std::forward<U>(u))... {}
};

template<class ...T>
struct tuple {
    typedef tuple_imp<typename make_tuple_indices<sizeof...(T)>::type, T...> base;
    
    base base_;
    
    tuple(const T& ...t)
        : base(typename make_tuple_indices<sizeof...(T)>::type(),
               typename make_tuple_types<tuple, sizeof...(T)>::type(),
               t...){}
};

看到了吧苟跪,每一個(gè)tuple都繼承自數(shù)個(gè)tuple_leaf。而前面說過蔓涧,每個(gè)tuple_leaf都有索引和值件已,所以定義一個(gè)tuple所需要的信息都保存在這些tuple_leaf中。如果有這樣的代碼

tuple(1, 2.0, 'a')

編譯器會(huì)展開成

struct tuple_imp : public tuple_leaf<0, int>,       // value = 1
                   public tuple_leaf<1, double>     // value = 2.0
                   public tuple_leaf<2, char>       // value = 'a'

是不是有種腦洞大開的感覺元暴?

make_tuple 和 get

為了方便使用篷扩,標(biāo)準(zhǔn)庫還定義了函數(shù)make_tupleget

// make_tuple

template<class T>
struct make_tuple_return_imp {
    typedef T type;
};

template<class T>
struct make_tuple_return {
    typedef typename make_tuple_return_imp<typename std::decay<T>::type>::type type;
};

template<class ...T>
inline tuple<typename make_tuple_return<T>::type...> make_tuple<T&& ...t) {
    return tuple<typename make_tuple_return<T>::type...>(std::forward<T>(t)...);
}

// get

template<size_t Index, class ...T>
inline typename tuple_element<Index, tuple<T...> >::type& get(tuple<T...>& t) {
    typedef typename tuple_element<Index, tuple<T...> >::type type;
    return static_cast<tuple_leaf<Index, type>&>(t.base_).get();

這些代碼我就不解釋了,留給你自己消化茉盏。

總結(jié)

本章展示的tuple只是個(gè)簡化版的示例而已鉴未,要實(shí)現(xiàn)工業(yè)強(qiáng)度的tuple,要做的工作還很多鸠姨。有興趣的同學(xué)可以去看看libc++源代碼铜秆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市享怀,隨后出現(xiàn)的幾起案子羽峰,更是在濱河造成了極大的恐慌,老刑警劉巖添瓷,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異值纱,居然都是意外死亡鳞贷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門虐唠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搀愧,“玉大人,你說我怎么就攤上這事疆偿≡凵福” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵杆故,是天一觀的道長迅箩。 經(jīng)常有香客問我,道長处铛,這世上最難降的妖魔是什么饲趋? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任拐揭,我火速辦了婚禮,結(jié)果婚禮上奕塑,老公的妹妹穿的比我還像新娘堂污。我一直安慰自己,他們只是感情好龄砰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布盟猖。 她就那樣靜靜地躺著,像睡著了一般换棚。 火紅的嫁衣襯著肌膚如雪式镐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天圃泡,我揣著相機(jī)與錄音碟案,去河邊找鬼。 笑死颇蜡,一個(gè)胖子當(dāng)著我的面吹牛价说,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播风秤,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼鳖目,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了缤弦?” 一聲冷哼從身側(cè)響起领迈,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碍沐,沒想到半個(gè)月后狸捅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡累提,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年尘喝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斋陪。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朽褪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出无虚,到底是詐尸還是另有隱情缔赠,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布友题,位于F島的核電站嗤堰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咆爽。R本人自食惡果不足惜梁棠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一置森、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧符糊,春花似錦凫海、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至模闲,卻和暖如春建瘫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尸折。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工啰脚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人实夹。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓橄浓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亮航。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荸实,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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