MIS.W 公式ブログ

早稲田大学公認、情報系創作サークル「早稲田大学経営情報学会」(MIS.W)の公式ブログです!

お前をメッタメタにしてやる【アドベントカレンダー2015冬13日目】

こんにちは49代のCashewです。

みなさん僕が誰か把握していますでしょうか。50代の人がとても多く把握しきれません。ごめんなさい。

ところで僕はC++erという俗にC++ばかりを使っている人間ですが皆さんはC++はお好きでしょうか?

え?Unityで使えないからC++できても意味がないって?

いやいや、UnityはC#の実行速度を改善するために。C#を一回C++に翻訳してネイティヴで実行できるようにするそうです。

いつからやるのか知りません。調べたらIL2CPPという機能でUnity5からすでに利用されているようです。

あっ、でも使えるのは結局C#だけみたいですね()。

 

さて、肝心のC++ですが、僕がC++の一番良いところだと思っているのはSTLの存在であり、そこについては新歓期のアドベントカレンダーで取り上げてしまいました。(リンク)

よって今回取り上げたい機能はあまりなかったのでテンプレートについて取り上げたいと思います。

テンプレートの基礎:関数テンプレート

テンプレートはコード内に型や数値を変数として代入したコードを新しく作れるようにする機能と言った方が良いでしょうか。

ここにサンプルコードを貼っときます。

#include <iostream>

template <typename T>
T getSquare(T t){
    return t*t;
}
int main(){
    std::cout<<getSquare<int>(10)<<std::endl;
    std::cout<<getSquare<int>(5.5)<<std::endl;
    std::cout<<getSquare<float>(10)<<std::endl;
    std::cout<<getSquare<float>(5.5)<<std::endl;
    std::cout<<getSquare(10)<<std::endl;
    std::cout<<getSquare(5.5)<<std::endl;
    return 0;
}

実行するとこのようになります。

100
25
100
30.25
100
30.25

template<typename T>の部分がテンプレートを使うときの宣言となります。

“template”は単にテンプレートを使うことをコンパイラに伝えているだけです。そして、次の<typename T>ですがこれが関数でいう(int a)の部分ですね。つまり引数リストです。

ここでは型がどの型であるかを渡したいため、typename、つまり型の名前を渡しているのです。今回はtemplateで最も良く使う型を渡していますが、これは型に限らず、intやfloatなども渡すことができます。つまり値なら渡すことができます。(constexprならいけるのかなぁ?)

今回は一つしかテンプレート引数を渡していませんが複数個渡すこともできます。

例: template<typename T, int  constValue>

これでtemplateの行は分かって頂けたでしょうか。

これは関数テンプレートという機能を利用したものです。ここではTというものに任意の型にすることができています。

templateはあたかも実行時に関数に型を与えているように見えますが実行時に型を渡しているわけではありません!!(多分これをわかっている人はもうテンプレートを使ったことがある人でしょう。)

ではどのようにしてこれが実行されるのか順に見ていきましょう。

まず、テンプレートが展開されるのは実行時ではなくコンパイル時です。

コンパイラの動きを考えてみましょう。

コンパイラはテンプレートの部分のコード(サンプルのgetSquareの定義の部分)を読んだだけで何かバイナリを生成できるでしょうか。答えは否ですね。

C++のバイナリは関数に渡す引数や内部で扱う変数、返り値の型(どのサイズのbitを弄ればいいのか、どのビットは何の情報を表しているのか)が分からなければバイナリを生成することが出来ません(例外あり)。

ではどの時にgetSquareのバイナリを生成するでしょうか。

答えはgetSquareを使ったコードが見つかった時です。

例えばコンパイラが std::cout<<getSquare<int>(10)<<std::endl; という行を読み込んだ時にgetSquareのTをintに置き換えたコードを生成します。

つまり

int getSquare(int t){
    return t*t;
}

というコードをコンパイルしたものを生成すると考えてください。

次に std::cout<<getSquare<float>(10)<<std::endl; というコードを読み込んだ時は

float getSquare(float t){
    return t*t;
}

というコードを生成するわけです。

ここで関数名が被ってることに気づいてしまったC言語マンもいるかもしれませんね。でも大丈夫。C++ではマングリングという機能があり、リンカはこれを正しく解釈することができます。

この説明でstd::cout<<getSquare<int>(5.5)<<std::endl;で25が出力された理由も大丈夫ですよね。

ではstd::cout<<getSquare(10)<<std::endl;というコード、つまりテンプレート引数がない場合も動きましたね。結果はfloatを使った時と同じになっています。ではテンプレート引数にはfloatが渡されたのでしょうか。doubleが渡されたのでしょうか。はたまたintが渡されたでしょうか。

答えは”関数の引数に渡した型と同じ”です。

つまりstd::cout<<getSquare(10)<<std::endl;のときint、 std::cout<<getSquare(5.5)<<std::endl;のときdoubleをテンプレート引数に渡したのと同じです。

floatを渡すにはどうしたらいいかって思いましたか?floatを渡す時はstd::cout<<getSquare(5.5f)<<std::ends;と書けばいいのです。(ここら辺はつまらないのでググってください。)

 

ここでtypename Tにクラスを渡すことも考えてみましょう。

#include <iostream>

class A{
    public:
    bool value;
    A():value(true){};
};
class B{
    public:
    bool value;
    B():value(false){};
};

template <typename T>
bool hogehoge(T t){
    return t.value;
}

int main(){
    std::cout<<hogehoge(A())<<std::endl;
    std::cout<<hogehoge(B())<<std::endl;
    return 0;
}

実はテンプレートは特殊化という機能があります。 特殊化を使うと何が嬉しいかというと例えば数値が渡された場合はこの関数、文字列が渡された場合はこの関数、クラスCが渡された場合はこの関数と言った具合に関数を切り替えることができます。同じ名前の関数でも関数を切り替えられるのです。では例を見てみましょう。以下はclass Aとclass Bは一般的な関数が使われ、class Cの場合のみ特殊化されて関数が呼び出されます。

#include <iostream>

class A{
    public:
    int value;
    A():value(1){};
};

class B{
    public:
    int value;
    B():value(2){};
};

class C{
    public:
    int value;
    C():value(3){};
};

//一般的なテンプレート関数
template <typename T>
int hogehoge(T t){
    return t.value;
}

//class Cに特殊化された関数
template<>
int hogehoge(C t){
    return -1;
}

int main(){
    std::cout<<hogehoge(A())<<std::endl;
    std::cout<<hogehoge(B())<<std::endl;
    std::cout<<hogehoge(C())<<std::endl;
    return 0;
}

では継承が絡んでくるとどうなるでしょうか class Aをスーパークラス、class Cがサブクラスとしてみましょう。ここでAについて特殊化した場合どうなるでしょうか。class Cはclass Aを継承しているのでclass Cに対してはclass Aの特殊化した関数が使われるでしょうか。

#include <iostream>

class A{
 public:
 int value;
 A():value(1){};
};

class B{
 public:
 int value;
 B():value(2){};
};

class C:public A{
 public:
 C(){
 value=3;
 };
};


template <typename T>
int hogehoge(T t){
 return t.value;
}

template<>
int hogehoge<A>(A t){
 return -1;
}

int main(){
 std::cout<<hogehoge(A())<<std::endl;
 std::cout<<hogehoge(B())<<std::endl;
 std::cout<<hogehoge(C())<<std::endl;
 return 0;
}

実行すると以下のようになります。

-1
2
3

どうやらclass Cに対しては一般的に定義した関数が利用されるようです。

これはテンプレートの特徴として覚えといてください。

テンプレートの基礎:クラステンプレート

次にクラステンプレートについて説明します。以下のコードがテンプレートクラスを使ってみたものです。

#include <iostream>
template <typename T>
class Vec2 {
public:
    T x,y;
    Vec2(T x,T y):x(x),y(y){}
};

int main(){

    Vec2<int> intVec=Vec2<int>(10.3, 2.5);
    Vec2<float> floatVec=Vec2<float>(10.3, 2.5);

    std::cout<<intVec.x<<std::endl;
    std::cout<<intVec.y<<std::endl;

    std::cout<<floatVec.x<<std::endl;
    std::cout<<floatVec.y<<std::endl;

    return 0;

}

最初のtemplate<T t> class の部分がテンプレートクラスを使うという宣言です。

ここのTに指定した方がclass内で使用しているTと入れ替わります。

それだけと言えばそれだけなのです。

ここのclass Vec2は要素の型に任意の整数型を入れることができるクラスです。

Vec2<int>とすればint型で要素を保持できるし、Vec2<float>とすれば要素がfloatで保持されます。

クラステンプレートでも特殊化などを使うことができます。

とりあえずここまで説明すればテンプレートクラスは使えると思うので説明はここまでにします。

中級編:メタ関数

ここでC++の標準ライブラリで使われている便利なテンプレートを紹介します。

#include <iostream>
#include <string>

class AbstructSomething{
    virtual void virtualFunc()=0;
};

class DerivedSomething :public AbstructSomething{
    typedef int int_A;
    void virtualFunc(){}
};

int main() {
    
    std::cout<<std::is_class<AbstructSomething>::value<<std::endl;
    std::cout<<std::is_class<int>::value<<std::endl;
    
    std::cout<<std::is_abstract<AbstructSomething>::value<<std::endl;
    std::cout<<std::is_abstract<DerivedSomething>::value<<std::endl;
    
    std::cout<<std::is_base_of<AbstructSomething, DerivedSomething>::value<<std::endl;
    std::cout<<std::is_base_of<AbstructSomething, void >::value<<std::endl;
    
    
    std::cout<<std::is_integral<int>::value<<std::endl;
    std::cout<<std::is_integral<std::string>::value<<std::endl;
    
    return 0;
}

このis_から始まるものはメタ関数と呼ばれます。関数と呼ばれますが実際にはクラステンプレートです。順に<>の中がクラスであるか、抽象クラスであるかどうか、その親かどうか、数値であるかどうかを判定する関数です。

この実行結果は以下のようになります。

1
0
1
0
1
0
1
0

例えばこんな風に実装されています。

#include <iostream>

template <typename T>
class my_is_void {
public:
    static constexpr bool value=false;
};

template <>
class my_is_void<void>{
public:
    static constexpr bool value=true;
    typedef void type;
};


int main(){
    
    std::cout<<my_is_void<void>::value<<std::endl;
    std::cout<<my_is_void<int>::value<<std::endl;
    
    return 0;
}

ここでは単にmy_is_voidというものにvoidを与えた時だけ特殊化することによって実装をしていますね。

中級編:SFINAE

ここでSFINAEというものについて説明をしたいと思います。SFINAEとはSubstitution Failure Is Not An Errorの略です。と言ってもわかりませんね。訳すると”テンプレートの置き換えに失敗した時はコンパイルエラーとしては扱われない”という意味です。

こうなると何が嬉しいでしょうか。幾つかの同じ名前のテンプレート関数/クラスが宣言されているとします。そんな中でmain関数からその関数を呼び出したとします。そうした時コンパイラがどの関数を使えばいいか分かるでしょうか。分かりますね。それぞれに適用し、一つだけに当てはまればコンパイルは成功です。逆に2つ以上当てはまってしまうとそれは曖昧な定義となりコンパイルが失敗してしまいます。また、一個も当てはまらなかった場合未定義エラーでコンパイルが失敗します。

C++コンパイラは静的に分かることであれば割と何でもやってくれます。

ただしここで注意点があり、SFINAEで意味している置き換えはテンプレート引数、関数の引数、関数の返り値の型のみです。つまりこの3つのうちいずれかでわざと置き換えを失敗させたり、選択的にそのクラス/テンプレートが使われるようにすればいいですね。

SFINAEの機能とメタ関数を使ってテンプレート引数に渡されたものが配列かどうかを判定して関数を振り分けてみましょう。

#include <iostream>

template <typename T>
void printType(typename std::enable_if<std::is_array<T>::value,void *>::type = nullptr){
    std::cout<<"This is Array."<<std::endl;
}
template <typename T>
void printType(typename std::enable_if<!std::is_array<T>::value,void *>::type = nullptr ){
    std::cout<<"This is not Array."<<std::endl;
}

int main(){
    printType<int[3]>();
    printType<int>();
    return 0;
}

最初のprintTypeが配列を渡した時に実行される関数です。 std::is_arrayが配列かどうかを判定するメタ関数です。テンプレート第一引数に渡したものが配列であれば::valueとするとbool値で配列かどうか、::typeでstd::true_type型を返します。(型を返すというかなんというか) std::enable_ifはテンプレートの第一引数に渡したbool値がtrueであれば::typeで第2引数で指定した型が得られます。ここではvoid*を指定しています。std::enable_ifのテンプレート引数の第2引数は省略可能で省略した場合はvoidを指定したのと同じになります。ここでは関数のデフォルト引数を設定する時にsfinaeを使って分岐しています。

これを使えば配列なら(is_array)これ、クラスだったら(is_class)これ、数値だったら(is_arismetic)これと分岐できますが、それ以外はどうやって作ればいいかわかりませんね。 そこで出てくるのが部分特殊化で分岐する方法です。部分特殊化とはテンプレート引数の一部だけを特殊化することです。どれが優先されるとかルールが存在し、一般的には最も特殊化されているものが使われます。(当然) しかしながら部分特殊化はテンプレート関数では利用できないため、テンプレートクラスで行います。(参考:http://mimosa-pudica.net/cpp-specialization.html) 下に3月位に書いてたソースコードでも貼っときます。

#include <iostream>
#include <vector>
template <class>
struct ignore{
    typedef void type;
};

template<typename T,typename = void>
class Test{
    public:
        static void out(){std::cout<<"non-STL"<<std::endl;}
};
template<typename T>
class Test<T,typename ignore<typename T::iterator*>::type>{
    public:
        static void out(){std::cout<<"STL"<<std::endl;}
};
template<typename T>
class Test<T,typename std::enable_if<std::is_array<T>::value>::type>{
    public:
        static void out(){std::cout<<"ARRAY"<<std::endl;}
};

int main(){
    Test<int>::out();
    Test<std::vector<int>>::out();
    Test<void>::out();
    Test<int[]>::out();
    return 0;
}

実行結果はこのようになります。

non-STL
STL
non-STL
ARRAY

STLであるかどうかはiteratorをTが持っているかどうかで分岐をしています。こうやって作っていけば自分の思い通りのSFINAEをかけますね。

 

長々と書きましたが、テンプレートの使い方わかっていただけましたでしょうか。 SFINAEはライブラリでも作らない限り必要性は薄いと思いますが、知ってるとライブラリの中身が読めるかもしれません。 ゴリゴリのTMPを期待してた方すみません。そういうのは他のサイトに譲ります。 みんな、オレオレライブラリ書こう。

この記事はCashewが課題をする時間を削って書かれました。Cashewの課題を手伝ってくれる人を募集しています。(ここが角丸に出来なかったorz)

C++マンなら上のネタわかってくれるよね。