C++11標準ライブラリソース分析:連載の7

9846 ワード

std::tuple
tuple簡史
C++Referenceのtupleに対する解釈は「fixed-size collection of heterogeneous values」,すなわち固定長の異性データの集合である.各C++コード仔がよく知っているstd::pairtupleです.ただし、std::pairは2つのデータしか収容できませんが、C++11標準ライブラリで定義されているtupleは、任意の複数、任意のタイプのデータを収容できます.
tupleの使い方
C++11標準ライブラリのtupleはテンプレートクラスで、使用時にヘッダファイルを含める必要があります.
#include 

using tuple_type = std::tuple;
tuple_type t1(1, 2.0, 'a');

しかし、私たちは一般的にstd::make_tuple関数を使用してtupleを作成します.std::make_tupleを使用するメリットは、tupleパラメータのタイプを指定する必要がなく、コンパイラが自分で推定することです.
#include 
#include 

auto t1 = std::make_tuple(1, 2.0, 'a');
std::cout << typeid(t1).name() << std::endl
std::get関数を使用して、tupleのデータを取り出すことができます.
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標準ライブラリには、tupleクラスの情報を取得するのに便利な補助クラスも定義されています.
using tuple_type = std::tuple;

// tuple_size:       tuple    
cout << std::tuple_size::value << endl; // 3

// tuple_element:       tuple     
cout << typeid(std::tuple_element<2, tuple_type>::type).name() << endl; // c
tupleの使い方について簡単に紹介します.C++Referenceではstd::tupleについて詳しく紹介されています.興味のある方は行ってみてください.次に、tupleの実現原理を再説明します.
tupleの実現原理
もしあなたがboost::tupleを知っているならば、boost::tupleは再帰的なネストを使用して実現されていることを知っておくべきで、これも多くのクラスライブラリ--例えばLokiとMS VC--実現tupleの方法です.libc++別の道を切り開き、多重継承の手法を採用して実現した.libc++tupleのソースコードは極めて複雑で、メタプログラミング技術を大量に使用しています.もし私が一行でこれらのソースコードを解読すれば、本章はC++テンプレートメタプログラミングの入門になります.あなたが引き続き見る勇気があるために、私はlibc++ tupleのソースコードを簡略化して、1つの極簡版tupleを実現して、あなたにtupleの仕事の原理を理解することを助けることができることを望みます.
tuple_size
まず、補助クラスから始めます.
// forward declaration
template class tuple;

template class tuple_size;

//   tuple     
template
class tuple_size > : public std::integral_constant {};

これは、tuple_sizeが1つのtupleに作用する場合、tuple_sizeの値がsizeof...(T)の値であることを理解しやすい.このように書くことができます
cout << tuple_size >::value << endl;    // 3

tuple_types
次の補助クラスはtuple_types:
template struct tuple_types{};

template::value, size_t Start = 0>
struct make_tuple_types {};

template
struct make_tuple_types, End, 0> {
    typedef tuple_types type;
};

template
struct make_tuple_types, End, 0> {
        typedef tuple_types type;
};
    

この簡略版のtyple_typesは具体的なことをしないで、純粋なタイプ定義です.なお、この簡略版のtuple_typesを使用する場合は、End == sizeof...(T) - 1を保証したほうがいいです.そうしないと、コンパイラがエラーを報告する可能性があります.
type_indices
次は少し複雑です.
template struct tuple_indices {};

template
struct integer_sequence {
    template
    using to_tuple_indices = tuple_indices;
};

template
using make_indices_imp = typename __make_integer_seq::template to_tuple_indices;

template
struct make_tuple_indices {
    typedef make_indices_imp type;
};
__make_integer_seqは、LLVMコンパイラに組み込まれた関数です.その役割は、その名の通りです.コンパイル中にシーケンスを生成します.このようなコードを書くと、次のようになります.
__mkae_integer_seq

コンパイラは次のように展開します.
integer_sequence<0>, integer_sequence<1>, integer_sequence<2>

したがって、次のコードについては、
make_tuple_indices<3>

コンパイラの最終展示会は次のとおりです.
tuple_indices<0>, tuple_indices<1>, tuple_indices<2>

これにより、tupleのインデックスが定義されます.
tuple_element
最後の補助クラスはtuple_element:
namespace indexer_detail {
    template
    struct indexed {
        using type = T;
    };
        
    template struct indexer;
        
    template
    struct indexer, tuple_indices > : public indexed... {};
        
    template
    indexed at_index(indexed const&);
} // namespace indexer_detail
    
template
using type_pack_element = typename decltype(indexer_detail::at_index(
    indexer_detail::indexer,
    typename make_tuple_indices::type>{}))::type;
    
template
struct tuple_element > {
    typedef type_pack_element type;
};
    
template
struct tuple_element > {
    typedef type_pack_element type;
};

私は上のコードがまたあなたをめまいがすることを知っているので、詳しく説明します.このようなコードを書くと:
tuple_element<1, tuple >::type

コンパイラコンベンション(煩わしいnamespace限定子を省略した後):tuple_pack_element<1, int, double, char>を開き、さらに展開する
decltype(
    at_index<1>(indexer, tuple_indices<3>>{})
)

なお、上記のコードでは、クラスindexerを関数at_indexのパラメータとして定義しているが、関数at_indexat_indexタイプのパラメータのみを受け入れているため、コンパイラはindexerindexed<1,double>に変換し、indexed<1, double>::typedoubleに変換する.
複雑そうに見えるが、文字の置き換えにほかならない.
tuple
はい、お酒の準備ができました.次はメイン料理です.
template
class tuple_leaf {
    Head value;

public:
    tuple_leaf() : vlaue(){}
    
    template
    explicit tuple_leaf(cosnt T& t) : value(t){}
    
    Head& get(){return value;}
    const Head& get() const {return value;}
};
tuple_leaftupleの基本構成単位であり、各tuple_leafにはインデックス(最初のテンプレートパラメータ)が保存され、値もあります.
続行:
template struct tuple_imp;

template
struct tuple_imp, T...> : 
    public tuple_leaf... {
    
    tuple_imp(){}
    
    template
    tuple_imp(tuple_indices, tuple_types, U&& ...u) 
        : tuple_leaf(std::forward(u))... {}
};

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

見たでしょう、それぞれのtupleは数のtuple_leafから継承されています.前述したように、各tuple_leafにはインデックスと値があるので、tupleを定義するために必要な情報は、これらのtuple_leafに保存されます.このようなコードがあれば
tuple(1, 2.0, 'a')

コンパイラコンベンション
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
使いやすいように、標準ライブラリでは、関数make_tuplegetも定義されています.
// make_tuple

template
struct make_tuple_return_imp {
    typedef T type;
};

template
struct make_tuple_return {
    typedef typename make_tuple_return_imp::type>::type type;
};

template
inline tuple::type...> make_tuple::type...>(std::forward(t)...);
}

// get

template
inline typename tuple_element >::type& get(tuple& t) {
    typedef typename tuple_element >::type type;
    return static_cast&>(t.base_).get();

これらのコードは私は説明しないで、あなたに自分で消化させます.
まとめ
本章で示すtupleは簡略化版の例にすぎず、工業強度のtupleを実現するには、まだ多くの仕事が必要である.興味のある学生はlibc++のソースコードを見に行くことができます.