プログラミング > 驚愕するC++ > 概要 > テンプレート


クラステンプレート

C++の目玉機能その2であるテンプレート。
こいつが最高にいかし(れ)てる。

基本は、任意の型を受け付けるだけ。

template <class T>
class C {
public:
	T t;
	C(T t) {
		this->t = t;
	}
};

int main() {
	C<int> c(100);
	C<double> c2(10.5);

	std::cout << c.t << std::endl;
	std::cout << c2.t << std::endl;

	std::cin.get();
	return 0;
}

どんな型でも受け付けるクラスCがあって、宣言時にその型を<>の中に入れて明示する。宣言のTがその型に置き換わって新たなクラスが構築されるという寸法だ。これがクラステンプレートというものである。

関数テンプレート

実は関数にも使える、もちろんメンバ関数にも使える、関数テンプレートがある。

template <class T>
void f(T t) {
	std::cout << sizeof(t) << std::endl;
}

int main() {
	f<int>(10); // 4
	f<double>(10.5); // 8

	std::cin.get();
	return 0;
}

関数テンプレートは、その関数の引数でテンプレートが完全に決定される場合、型を省略できて、

f(10); // 4
f(10.5); // 8

と書ける。

関数テンプレートも所詮関数だから、オーバーロードが可能である。この際、展開したときに同一のシグネチャが生成されるとコンパイルエラーとなる。どちらかわからないので……

template <class T>
void f(T t) {
	std::cout << "f(T t)" << std::endl;
}

template <class T>
void f(T& t) {
	std::cout << "f(T& t)" << std::endl;
}

template <class T>
void f(T&& t) {
	std::cout << "f(T&& t)" << std::endl;
}

テンプレート仮引数Tを用いてT&やT&&と表記できる。もちろんconst Tとか、T*とかT[]などは問題ないのだが、参照に関しては扱いが特別である。上記の例ではf(10);だけだとコンパイルエラーとなる。

どういう仕様かというと、T&は、T&が強制的に左辺値参照として扱われ、T&&はTと同じだと扱われる、という仕様だ。従って、上記の例ではT&&がTと同じと見なされコンパイルできない。

右辺値参照を用いる場合は、f(T)に対してf<int&&>(value)としなければならない(みたい)。

値テンプレート引数

テンプレートは任意の型だけでなく、型の値を受け付けることもできる。クラステンプレートでも同様である。

template <int n>
void f() {
	std::cout << n << std::endl;
}

int main() {
	f<10>();
	f<100>();
}

テンプレートの特殊化

テンプレートを定義して、特定の型だけは別にしたい場合テンプレートの特殊化と呼ばれる書き方をする。以下の例では、<class S, class T>というテンプレートを受け付ける関数fに対して、Sがintのときだけ別の関数をオーバーロード(のように)するように記述している。

#include <iostream>

class C {
public:
	C() {
		std::cout << "In C" << std::endl;
	}
};

template <class S, class T>
void f(S c) {
	T t;
	std::cout << "S : " << c << std::endl;
}

template <>
void f<int, C>(int n) {
	std::cout << "int : " << n << std::endl;
}

int main() {
	f<int, C>(100);
	f<double, C>(10.5);

	std::cin.get();
	return 0;
}

特殊化したい関数やクラスの前に「template <>」と書き、関数名やクラス名に対して<>でテンプレートの仮引数を与える。今回はint型とCのペアとした。

特定のメンバ関数だけ特殊化

次のように、あるクラステンプレートCのC<int>だけメソッドを変えると言ったことが可能である。

template <class T>
struct C {
	void method() { cout << "その他" << endl; }
};

template <>
void C<int>::method() { cout << "int" << endl; }

これに対して、次のように変化する。

C<int> a;
a.method(); // int
C<double> b;
b.method(); // その他

メンバのテンプレートの特殊化

あるテンプレートクラスC<T>があってそのメンバにテンプレートクラスC<T>::S<U>があるとする。このとき、C<T>::S<U>を特殊化して、次のように書くことはできない。

template <class T>
template <>
C<T>::S<int>; // これはできない

なお、次のように全て特殊化することなら可能である。おそらく実装が複雑になるため禁止にしているのだろう。

template <>
template <>
C<double>::S<int>; // これはできない

どうしてもメンバ関数を特殊化したいなら、次のように適当な新たな関数テンプレート(_method)を作ってmethodから呼ぶなどの方法がある。

template <class T>
struct C {
	template <class U>
	struct S {
		void method() {
			_method<U>();
		}
	private:
		template <class _U>
		void _method() {
			cout << "default" << endl;
		};

		template <>
		void _method<int>() {
			cout << "int" << endl;
		};
	};

	S<T> s;
};

テンプレートの部分特殊化

クラステンプレートに対してはテンプレートの部分特殊化というものができる。以下のコードを見て頂きたい。

template <class T>
class C {
public:
	C() {
		std::cout << "In C<class T>" << std::endl;
	}
};

template <class T>
class C<T*> { // 既にあるC<class T>のうち、「class Tを用いて」T*で特殊化している
public:
	C() {
		std::cout << "In C<class T*>" << std::endl;
	}
};

int main() {
	C<int> c;
	C<int*> c2;

	std::cin.get();
	return 0;
}

このように、既にあるテンプレートを、新しくテンプレート引数で取得してそれを用いて特殊化している。関数の場合はこれができない。

テンプレートの別名

クラステンプレートに対して別名を付けることもできる。これは新しい機能である。例えば次のようなクラステンプレートがあったとしよう。

template <class S, class T>
class C {

};

このクラステンプレートに対して、Sは毎回変えたいが、Tはint固定のクラステンプレートが欲しいとする。
ただし、部分特殊化する必要ないとする。こういう場合は、

template <class S>
typedef C<S, int> NewCC;

のようにtypedefしたいが、これは残念ながら対応していない。ここで新しいusing構文を使って、

template <class S>
using NewC = C<S, int>;

とすることで、

NewC<int> new_c;

と呼び出せる。

可変長引数のテンプレート

新しい機能のもう一つにテンプレートの可変長引数がサポートされた。これにより、任意の数のテンプレートを指定できる。

template <class ...Ts>
class C {

};

このようにすることで任意の数のテンプレートを受け取れる。...演算子は引数のTsの前に現れるとpack演算子、後に現れるとunpack演算子として振る舞う。pack演算子は、任意の数の引数をまとめる、unpack演算子はそれを展開する役割を持つ。コンストラクタも同様で、次のようにpack演算子を使って書ける。

template <class ...Ts>
class C {
public:
	C(Ts ...ts) {

	}
};

テンプレート・テンプレート引数

次の例を考えよう。今、CをC<TC, int>で呼び出したい。TCはTC<int>で呼ばれることを想定する。つまり、C<TC, ???>を呼ぶと、内部でTC<???>を定義するという格好だ。ところが以下の例はコンパイルが通らない。それもそのはず、テンプレート引数であるclass Tはテンプレートでないから、T<???>とできないのだ。そこで、この引数がテンプレートであることを示すためにtemplate<class>を前に入れる

template<class T, class S>
class C {
public:
	C(T<S> t) : t(t) {
		std::cout << typeid(*this).name() << std::endl;
	}

	T<S> t;
};

template<class T>
class TC {
public:
	TC() {
		std::cout << typeid(*this).name() << std::endl;
	}
};

以下のように変更する。

template<template<class>class T, class S>

これはテンプレート・テンプレート引数と呼ばれる。利用例を見てみよう。

template<template<class> class TC, class T, class ...Ts>
class C {
public:
	TC<T> t;
	C<TC, Ts...> c;

	C(T t, Ts... ts) : t(t), c(ts...) {
		std::cout << typeid(*this).name() << std::endl;
	}
};

template<template<class> class TC, class T>
class C<TC, T> {
public:
	TC<T> t;

	C(T t) : t(t) {
		std::cout << typeid(*this).name() << std::endl;
	}
};

template<class T>
class A {
public:
	T value;

	A(T t) : value(t) {
		std::cout << typeid(*this).name() << std::endl;
	}
};

int main() {
	C<A, int, double> c(10, 10.5);

	std::cout << c.t.value << std::endl;
	std::cout << c.c.t.value << std::endl;

	std::cin.get();
	return 0;
}

実行結果は
class A<int>
class A<double>
class C<class A,double>
class C<class A,int,double>
10
10.5
となる。typeid(*this).name()は実行時に型の名前を取得するものである。再帰的にコンストラクタが呼び出されて定義されている様子が分かる。また、今回の要となるテンプレート・テンプレート引数によって、A<なんちゃら>がint, doubleで呼び出されているのが分かる。

テンプレートのデフォルト引数

template<class T = int>
とすれば、何も書かなかったとしたらintとして扱われるようになる。

テンプレート・メタプログラミング

コンパイル時にコードを生成することができる。このようなものをメタプログラミングと言うが、テンプレートを使って実現できる。もちろん、テンプレート以外でもマクロを使って実現できることは周知の通りである。

そして、このテンプレート・メタプログラミングは、最恐にして最悪の黒魔術と呼ばれている。

戻り値の表現

執筆中

繰り返しの表現

執筆中

条件分岐の表現

執筆中

テンプレートの例

トレイト

ある型に応じて、そのクラスの型が複数変化する場合がある。ある型に対して複数の型を定義し、その複数の型を使ってクラスを定義するテクニックがある。これをトレイトといい、以下のようにできる。

template<class T>
struct C_traits;

// int→int
template<>
struct C_traits<int> {
	using type = int;
};

// double→double
template<>
struct C_traits<double> {
	using type = double;
};

// 上記traitsを使って定義
template<class traits>
class C {
	using T = typename C_traits<traits>::type; // typeを使う(以下Tとする)
	T data;

public:
	C(T data) : data(data) { }
	
	void show() {
		cout << data << endl;
	}
};

クラスCはトレイトを使って、型に依存しないコードを書くことができる。本来ならテンプレートがそのためのものであるが、C_taraits<type>を定義することで、ある型に応じて様々な静的メンバ関数やそれに追従する型・定数などを定義することができる。

ダックタイピング

関数テンプレートにおいて、仮引数にて任意の型を受け取れる。そして、その引数のメンバ関数を呼び出すことができる。ただし、そのメンバ関数は実行時にそのクラスに応じて呼び出されるため、同じメンバ関数名でもクラスが違えばそのクラスのものが実行される。

通常、同じメンバ関数名でクラスによって動作を変えるには、インターフェイスや抽象クラスなどを用いて実現する。これについてはクラスで述べた。このことをポリモーフィズムといったが、実はC++ではポリモーフィズムを、テンプレートを使ったダックタイピングと呼ばれる方法で実現することができる。

ダックタイピングとは、同じメンバ関数名であれば、どんなクラスだろうが、同じ目的を果たすメンバ関数だという考えに基づいて処理をする方法である。C++ではテンプレートを使って実現できる。

class A {
public:
	void method() {
		std::cout << "A::method()" << std::endl;
	}
};

class B {
public:
	void method() {
		std::cout << "B::method()" << std::endl;
	}
};

template<class T>
void f(T t) {
	t.method(); // methodがあればそれを実行
}

int main() {
	A a;
	B b;

	f(a); // A::method()
	f(b); // B::method()

	std::cin.get();
	return 0;
}

パラメータ化継承

class C {
public:
	void func(int x) {
		cout << "In C::func(" << x << ")" << endl;
	}
};

template<class Base>
class A : public Base {
public:
	void func(int x) {
		cout << "In A::func(" << x << ")" << endl;
		Base::func(x + 1);
	}
};

template<class Base>
class B : public Base {
public:
	void func(int x) {
		cout << "In B::func(" << x << ")" << endl;
		Base::func(x * 2);
	}
};
int main() {
	A<B<A<C>>> a;
	a.func(1);
	return 0;
}
実行結果は、
In A::func(1)
In B::func(2)
In A::func(4)
In C::func(5)
である。

このようにパラメータにテンプレートを用いて、そのクラスを基底クラスにするという手法である。これは、次々に基底クラスを追加していく。従って、パラメータに存在するクラスの分だけコンストラクタが呼び出される。

CRTP

CRTP(Curiously Reccursive/Reccuring Template Pattern)は、異なるクラスでも、その型に応じた処理が同じである場合にコードが再利用できるようにするためのイディオムである。

template<class Sub>
class Base {
public:
	void interface() {
		((Sub*)this)->func(); // これはダウンキャストである!
	}
};

class C : public Base<C> {
public:
	void func() {
		cout << "In C::func()" << endl;
	}
};

class C2 : public Base<C2> {
public:
	void func() {
		cout << "In C2::func()" << endl;
	}
};

int main() {
	C c;
	c.interface();
	C2 c2;
	c2.interface();
	return 0;
}

この例では分かりづらいが、どちらもinterfaceを通じて、型に依存するがその型のfuncを呼び出している。この例では、別にinterfaceを通じて何かする意味はないが、様々なクラスで共通のインターフェイスを提供するのに使える。ただのインターフェイスではそのクラスが使えないのに対し、こちらは使えるという点が異なる。

これを使った応用例として、Mix-Inがある。これは、継承の構造に手入れせずに、メソッドを追加しようとする試みである。例えば次の例では、Cとその派生Subがあって、SubはMixInしてfunc関数をもっている。

#include <iostream>

using namespace std;

template <class T>
class MixIn {
	friend typename T; // MixInの初期化ができるようにTだけ許可(フレンド)

public:
	void func() {
		cout << self.getNumber() << endl; // self = Tのインスタンス
	}
	
private:
	MixIn(T& self) : self(self) { } // Tの参照を受け取って保存
	MixIn() = delete; // MixIn単体では使用不可
	MixIn<T>& operator=(MixIn<T>&) = delete; // MixIn単体では使用不可

	T& self;
};

class C {
public:
	C() { }

	void setX(int x) {
		this->x = x;
	}

	int getNumber() {
		return x;
	}

private:
	int x;
};

class Sub : public C, public MixIn<Sub> { // MixInしている!
public:
	Sub() : MixIn<Sub>(*this) { } // MixInの初期化T = Sub
};

int main() {
	Sub sub;
	sub.setX(10);
	sub.func(); // Mix-Inされた関数

	getchar();
	return 0;
}
最終更新:2014年03月05日 15:18