予鈴

競プロとか備忘録とか…

vectorsのテンプレートを詳しく見る

はじめに

競プロやってると、多次元のvectorをよく使うことがあります。非常に便利なんだけど、宣言がめんどくさい。
例えば、3次元のvectorを宣言しようとすると、

    vector<vector<vector<int>>>v(10,vector<vector<int>>(10,vector<int>(10,1000)));

こうなります。
4次元はもう書く気すらなくなってしまいます。しかしvectorsのテンプレートを用いると、

 auto v = vectors(10,10,10,3);

このように簡単に書くことができます。
N+1個の引数に対して、最後の数値を初期値としてN次元のvectorを生成できます。
今回はこのテンプレートを詳しく見ていくことにします(勉強を兼ねて)

vectorsテンプレート

template<typename Head, typename Value>
 auto vectors(const Head &head, const Value &v) {
            return vector<Value>(head, v);
 }

template<typename Head, typename... Tail>
 auto vectors(Head x, Tail... tail) { 
       auto inner = vectors(tail...);
   return vector<decltype(inner)>(x, inner); }

vectorsのテンプレートは2つのvectorsという関数から成り立っています。
これを詳しく見ていきます。

可変引数テンプレート

任意のNについてvectorを生成するので、引数は毎度変化します。
これに対応するために、可変引数テンプレート*1が用いられています。
サンプルコードを見て見ましょう。

#include<bits/stdc++.h>
using namespace std;
template<class ...Args>
void func(Args... arg){
   //argの数を表示
    cout << sizeof...(arg) << endl;
}
template<class Head, class ...args> void func2 (Head a,args... b){
    cout << a  << endl;
    cout << sizeof...(b) << endl;
}
signed main(){
    func(1,2,3,4,5,6,7,8);
    func2(1,2.2,"3",'4');
   /* output:
      8
      1
      3
   */
}

可変引数テンプレートでは、任意のと任意の引数をテンプレートとして利用することができます。
上記のfunc関数では
・Argsがテンプレートパラメータパック
・argsをが関数パラメータパック
となっています。それぞれ、推論された引数が格納されています。
関数内でパラメータパックを用いる際は ... を用いて展開します。
func2に注目してみると、引数として渡された引数が、Head 型の a と args型のbに分割されています。f:id:yebityon:20180609151625p:plain

この関数を繰り返し用いると、やがてargs...の引数は一つになります。
勘がいい方はすでに気づいているかもしれませんが、これは繰り返し処理に用いることができます。
具体例を示します。

#include<bits/stdc++.h>
using namespace std;
template<class Head, class args>    void func2 (Head a,args b){
    cout << a << endl;
    cout << b << endl;
}
template<class Head, class... args> void func2 (Head a,args... b){
    func2(b...);

}signed main(){;
    func2(1,2.2,"3",'4');
   /*output
    3
    4
   */

}

複数の引数が渡された関数は、再帰的にfunc2関数を呼び、最後に引数の引数の数は2つになります。
このように、関数をオーバーロードすることで、タグディスパッチによって最適な関数が呼び出されます。

decltypeについて

上記の知識とautoがsyntax sugarであることがわかれば、ほとんど中身が理解できる…かもしれませんが、最後にdecltypeだけ説明しておきます。
decltypeとは、指定した式から型取得する機能です。

#include<bits/stdc++.h>
using namespace std;
signed main(){
    cout << sizeof(decltype(2.2+ 3.4)) << endl; // decltypeはdouble型
    cout << sizeof(decltype('a' )) << endl;  //decltypeはchar 型
    cout << sizeof(decltype(2+1))<<endl; //decltypeはint型
    /* output
     8
     1
     4
     */
}

vectorsテンプレートでは、decltypeで取得した型とするvectorを返すことで、入れ子構造を達成しています。

*1:任意の数でテンプレートを引数をとれるテンプレートを宣言できる機能